OSDN Git Service

Fix hb_log to truncate the message at the correct point, fixes 1244.
[handbrake-jp/handbrake-jp-git.git] / macosx / HBQueueController.mm
index d657fc8..594bce0 100644 (file)
 #include "Controller.h"
 #import "HBImageAndTextCell.h"
 
-#define HB_QUEUE_DRAGGING 0        // <--- NOT COMPLETELY FUNCTIONAL YET
-#define HBQueueDataType            @"HBQueueDataType"
+#define HB_ROW_HEIGHT_TITLE_ONLY           17.0
 
-// 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
+// Pasteboard type for or drag operations
+#define HBQueuePboardType            @"HBQueuePboardType"
 
-#define HB_ROW_HEIGHT_TITLE_ONLY           17.0
-#define HB_ROW_HEIGHT_TITLE_WITH_SUMMARY   45.0
-#define HB_ROW_HEIGHT_INDEPTH_PASS         17.0
-#define HB_ROW_HEIGHT_1ST_PASS             54.0
-#define HB_ROW_HEIGHT_2ND_PASS             67.0
+//------------------------------------------------------------------------------------
+// Job ID Utilities
+//------------------------------------------------------------------------------------
+
+int MakeJobID(int jobGroupID, int sequenceNum)
+{
+    return jobGroupID<<16 | sequenceNum;
+}
 
-//#define HB_ROW_HEIGHT_NO_DETAIL    17.0
-//#define HB_ROW_HEIGHT_ACTIVE_JOB   60.0
+bool IsFirstPass(int jobID)
+{
+    return LoWord(jobID) == 0;
+}
 
 //------------------------------------------------------------------------------------
-#pragma mark Job group functions
+// NSMutableAttributedString (HBAdditions)
 //------------------------------------------------------------------------------------
-// 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)    
+@interface NSMutableAttributedString (HBAdditions)
+- (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary;
+@end
+
+@implementation NSMutableAttributedString (HBAdditions)
+- (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary
 {
-    hb_job_t * job;
-    int count = 0;
-    int index = 0;
-    while( ( job = hb_job( h, index++ ) ) )
-    {
-        if (job->sequence_id == 0)
-            count++;
-    }
-    return count;
+    NSAttributedString * s = [[[NSAttributedString alloc]
+        initWithString: aString
+        attributes: aDictionary] autorelease];
+    [self appendAttributedString: s];
 }
+@end
+
+//------------------------------------------------------------------------------------
+#pragma mark -
+//------------------------------------------------------------------------------------
+
+@implementation HBQueueOutlineView
 
-/**
- * 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)    
+- (void)viewDidEndLiveResize
 {
-    hb_job_t * job;
-    int count = 0;
-    int index = 0;
-    while( ( job = hb_job( h, index++ ) ) )
+    // 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];
+        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)
         {
-            if (count == i)
-                return job;
-            count++;
+            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];
+    [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 (withIcon)
+        {
+            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 (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;
+            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
-            index++;
+        {
+            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, jobAudioCodec];
+        else
+            jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio\n", jobFormatInfo, jobVideoCodec, jobAudioCodec];
+            
+        [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)
+    {
+        NSString * jobAudioInfo;
+        if ([jobAudioCodec isEqualToString: @"AC3"])
+            jobAudioInfo = [NSString stringWithFormat:@"%@, Pass-Through", jobAudioCodec];
+        else
+            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]
+        int ai; // counter for each audios [] , macgui only allows for two audio tracks currently
+        for( ai = 0; ai < 2; ai++ )
+        {
+            if (audio_mixdowns[ai] == HB_AMIXDOWN_MONO)
+                jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Mono", ai + 1]];
+            if (audio_mixdowns[ai] == HB_AMIXDOWN_STEREO)
+                jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Stereo", ai + 1]];
+            if (audio_mixdowns[ai] == HB_AMIXDOWN_DOLBY)
+                jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Surround", ai + 1]];
+            if (audio_mixdowns[ai] == HB_AMIXDOWN_DOLBYPLII)
+                jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Pro Logic II", 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:detailBoldAttr];
+        [finalString appendString: @"Audio: " withAttributes:detailBoldAttr];
+        [finalString appendString:[NSString stringWithFormat:@"%@\n", jobAudioInfo] 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;
 }
 
-#if HB_OUTLINE_QUEUE
-/**
- * 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 );
+        _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;
 }
-#endif
 
-#pragma mark -
-//------------------------------------------------------------------------------------
-// HBJob
-//------------------------------------------------------------------------------------
++ (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;
+}
 
-#if HB_OUTLINE_QUEUE
++ (NSDictionary *) descriptionTitleAttribute
+{
+    if (!_titleAttribute)
+        _titleAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
+                [NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName,
+                _descriptionParagraphStyle, NSParagraphStyleAttributeName,
+                nil] retain];
+    return _titleAttribute;
+}
 
-@interface HBJob : NSObject
++ (NSDictionary *) descriptionShortHeightAttribute
 {
-    hb_job_t                *fJob;
+    if (!_shortHeightAttribute)
+        _shortHeightAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
+                [NSFont systemFontOfSize:2.0], NSFontAttributeName,
+                nil] retain];
+    return _shortHeightAttribute;
 }
-+ (HBJob*) jobWithJob: (hb_job_t *) job;
-- (id) initWithJob: (hb_job_t *) job;
-- (hb_job_t *) job;
+
+
 @end
 
-@implementation HBJob
-+ (HBJob*) jobWithJob: (hb_job_t *) job
+#pragma mark -
+
+//------------------------------------------------------------------------------------
+// HBJobGroup
+//------------------------------------------------------------------------------------
+
+// Notification sent from HBJobGroup setStatus whenever the status changes.
+NSString *HBJobGroupStatusNotification = @"HBJobGroupStatusNotification";
+
+@implementation HBJobGroup
+
++ (HBJobGroup *) jobGroup;
 {
-    return [[[HBJob alloc] initWithJob:job] autorelease];
+    return [[[HBJobGroup alloc] init] autorelease];
 }
 
-- (id) initWithJob: (hb_job_t *) job
+- (id) init
 {
     if (self = [super init])
     {
-        // job is not owned by HBJob. It does not get dealloacted when HBJob is released.
-        fJob = job;
+        fJobs = [[NSMutableArray arrayWithCapacity:0] retain];
+        fDescription = [[NSMutableAttributedString alloc] initWithString: @""];
+        [self setNeedsDescription: NO];
+        fStatus = HBStatusNone;
     }
     return self; 
 }
 
-- (hb_job_t*) job
+- (void) dealloc
 {
-    return fJob;
+    [fPresetName release];
+    [fJobs release];
+    [super dealloc];
 }
 
-@end
+- (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
 
-#endif // HB_OUTLINE_QUEUE
 
 #pragma mark -
 
@@ -168,11 +841,8 @@ static hb_job_t * hb_next_job( hb_handle_t * h, hb_job_t * job )
 static NSString*    HBQueueToolbar                            = @"HBQueueToolbar1";
 static NSString*    HBQueueStartCancelToolbarIdentifier       = @"HBQueueStartCancelToolbarIdentifier";
 static NSString*    HBQueuePauseResumeToolbarIdentifier       = @"HBQueuePauseResumeToolbarIdentifier";
-#if !HB_OUTLINE_QUEUE
-static NSString*    HBShowDetailToolbarIdentifier             = @"HBQueueShowDetailToolbarIdentifier";
-static NSString*    HBShowGroupsToolbarIdentifier             = @"HBQueueShowGroupsToolbarIdentifier";
-#endif
 
+#pragma mark -
 
 @implementation HBQueueController
 
@@ -190,17 +860,14 @@ static NSString*    HBShowGroupsToolbarIdentifier             = @"HBQueueShowGro
             @"YES",     @"QueueShowsJobsAsGroups",
             nil]];
 
-#if HB_OUTLINE_QUEUE
-        fShowsDetail = YES;
-        fShowsJobsAsGroups = YES;
-#else
-        fShowsDetail = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsDetail"];
-        fShowsJobsAsGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
-#endif
+        fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain];
 
-#if HB_OUTLINE_QUEUE
-        fEncodes = [[NSMutableArray arrayWithCapacity:0] retain];
-#endif
+        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; 
 }
@@ -210,16 +877,16 @@ static NSString*    HBShowGroupsToolbarIdentifier             = @"HBQueueShowGro
 //------------------------------------------------------------------------------------
 - (void)dealloc
 {
-    [fAnimation release];
-    
     // clear the delegate so that windowWillClose is not attempted
     if ([fQueueWindow delegate] == self)
         [fQueueWindow setDelegate:nil];
     
-#if HB_OUTLINE_QUEUE
-    [fEncodes release];
+    [fJobGroups release];
+    [fCurrentJobGroup release];
     [fSavedExpandedItems release];
-#endif
+    [fSavedSelectedItems release];
+
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
 
     [super dealloc];
 }
@@ -240,30 +907,44 @@ static NSString*    HBShowGroupsToolbarIdentifier             = @"HBQueueShowGro
     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;
+}
 
-    [self updateQueueUI];
-    [self updateCurrentJobUI];
+//------------------------------------------------------------------------------------
+// Returns the HBJob (pass) that is currently being encoded; nil if no encoding is
+// occurring.
+//------------------------------------------------------------------------------------
+- (HBJob *) currentJob
+{
+    return fCurrentJob;
+}
 
-    [fQueueWindow makeKeyAndOrderFront: self];
+#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).
 //------------------------------------------------------------------------------------
 - (void) showCurrentJobPane: (BOOL)showPane
 {
-    if (showPane != fCurrentJobHidden)
+    if (showPane == fCurrentJobPaneShown)
         return;
     
     // Things to keep in mind:
@@ -296,805 +977,331 @@ static NSString*    HBShowGroupsToolbarIdentifier             = @"HBQueueShowGro
         [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];
-    fCurrentJobHidden = !showPane;
-}
-
-//------------------------------------------------------------------------------------
-// Enables or disables the display of detail information for each job.
-//------------------------------------------------------------------------------------
-- (void)setShowsDetail: (BOOL)showsDetail
-{
-#if HB_OUTLINE_QUEUE
-    return; // Can't modify this value. It's always YES.
-#else
-    fShowsDetail = showsDetail;
+    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];
     
-    [[NSUserDefaults standardUserDefaults] setBool:showsDetail forKey:@"QueueShowsDetail"];
-    [[NSUserDefaults standardUserDefaults] synchronize];
-
-    [fTaskView setRowHeight:showsDetail ? HB_ROW_HEIGHT_DETAIL : HB_ROW_HEIGHT_NO_DETAIL];
-  #if HB_UNI_QUEUE
-    if (hb_count(fHandle))
-        [fTaskView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndex:0]];
-  #endif
-    if ([fTaskView selectedRow] != -1)
-        [fTaskView scrollRowToVisible:[fTaskView selectedRow]];
-#endif
+    fCurrentJobPaneShown = showPane;
 }
 
 //------------------------------------------------------------------------------------
-// Enables or disables the grouping of job passes into one item in the UI.
+// Sets fCurrentJobGroup to a new job group.
 //------------------------------------------------------------------------------------
-- (void)setShowsJobsAsGroups: (BOOL)showsGroups
+- (void) setCurrentJobGroup: (HBJobGroup *)aJobGroup
 {
-#if HB_OUTLINE_QUEUE
-    return; // Can't modify this value. It's always YES.
-#else
-    fShowsJobsAsGroups = showsGroups;
-    
-    [[NSUserDefaults standardUserDefaults] setBool:showsGroups forKey:@"QueueShowsJobsAsGroups"];
-    [[NSUserDefaults standardUserDefaults] synchronize];
+    if (aJobGroup)
+        [aJobGroup setStatus: HBStatusWorking];
 
-    [self updateQueueUI];
-    if ([fTaskView selectedRow] != -1)
-        [fTaskView scrollRowToVisible:[fTaskView selectedRow]];
-#endif
+    [aJobGroup retain];
+    [fCurrentJobGroup release];
+    fCurrentJobGroup = aJobGroup;
 }
 
-//------------------------------------------------------------------------------------
-// Returns a 16x16 image that represents a job pass.
-//------------------------------------------------------------------------------------
-- (NSImage *)smallImageForPass: (int)pass
-{
-    switch (pass)
-    {
-        case -1: return [NSImage imageNamed: @"JobPassSubtitleSmall"];
-        case  0: return [NSImage imageNamed: @"JobPassFirstSmall"];
-        case  1: return [NSImage imageNamed: @"JobPassFirstSmall"];
-        case  2: return [NSImage imageNamed: @"JobPassSecondSmall"];
-        default: return [NSImage imageNamed: @"JobPassUnknownSmall"];
-    }
-}
+#pragma mark - Finding job groups
 
 //------------------------------------------------------------------------------------
-// Returns a 64x64 image that represents a job pass.
+// Returns the first pending job with a specified destination path or nil if no such
+// job exists.
 //------------------------------------------------------------------------------------
-- (NSImage *)largeImageForPass: (int)pass
+- (HBJobGroup *) pendingJobGroupWithDestinationPath: (NSString *)path
 {
-    switch (pass)
+    HBJobGroup * aJobGroup;
+    NSEnumerator * groupEnum = [fJobGroups objectEnumerator];
+    while ( (aJobGroup = [groupEnum nextObject]) )
     {
-        case -1: return [NSImage imageNamed: @"JobPassSubtitleLarge"];
-        case  0: return [NSImage imageNamed: @"JobPassFirstLarge"];
-        case  1: return [NSImage imageNamed: @"JobPassFirstLarge"];
-        case  2: return [NSImage imageNamed: @"JobPassSecondLarge"];
-        default: return [NSImage imageNamed: @"JobPassUnknownLarge"];
+        if ([[aJobGroup destinationPath] isEqualToString: path])
+            return aJobGroup;
     }
+    return nil;
 }
 
-#if HB_OUTLINE_QUEUE
 //------------------------------------------------------------------------------------
-// Rebuilds the contents of fEncodes which is a array of encodes and HBJobs.
+// Locates and returns a HBJob whose sequence_id matches a specified value.
 //------------------------------------------------------------------------------------
-- (void)rebuildEncodes
+- (HBJob *) findJobWithID: (int)aJobID
 {
-    [fEncodes autorelease];
-    fEncodes = [[NSMutableArray arrayWithCapacity:0] retain];
-
-    NSMutableArray * aJobGroup = [NSMutableArray arrayWithCapacity:0];
-
-    hb_job_t * nextJob = hb_group( fHandle, 0 );
-    while( nextJob )
+    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 fEncodes and then start a new one.
-            if ([aJobGroup count] > 0)
-            {
-                [fEncodes addObject:aJobGroup];
-                aJobGroup = [NSMutableArray arrayWithCapacity:0];
-            }
+            if (job->sequence_id == aJobID)
+                return job;
         }
-        [aJobGroup addObject: [HBJob jobWithJob:nextJob]];
-        nextJob = hb_next_job (fHandle, nextJob);
-    }
-    if ([aJobGroup count] > 0)
-    {
-        [fEncodes addObject:aJobGroup];
     }
+    return nil;
 }
-#endif
 
-#if HB_OUTLINE_QUEUE
 //------------------------------------------------------------------------------------
-// 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.
+// Locates and returns a libhb job whose sequence_id matches a specified value.
 //------------------------------------------------------------------------------------
-- (void) saveOutlineViewState
+- (hb_job_t *) findLibhbJobWithID: (int)aJobID
 {
-    if (!fSavedExpandedItems)
-        fSavedExpandedItems = [[NSMutableIndexSet alloc] init];
-    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.
-    
-    id anEncode;
-    NSEnumerator * e = [fEncodes objectEnumerator];
-    while ( (anEncode = [e nextObject]) )
-    {
-        if ([fOutlineView isItemExpanded: anEncode])
-            [fSavedExpandedItems addIndex: (unsigned int)[[anEncode objectAtIndex:0] job]];
-    }
-    
-    // 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. So here's the strategy. If a *group*
-    // object is selected, then the first hb_job_t item in the group is saved in
-    // fSavedSelectedItem. If a job is selected, then its hb_job_t item is saved. To
-    // distinguish between a group being selected vs an actual job, the high bit of
-    // fSavedSelectedItem is set when it refers to a job object. I know, not pretty.
-    // This could go away if I'd save a unique id in each job object.
-
-    int selection = [fOutlineView selectedRow];
-    if (selection == -1)
-        fSavedSelectedItem = 0;
-    else
+    hb_job_t * job;
+    int index = 0;
+    while( ( job = hb_job( fHandle, index++ ) ) )
     {
-        id obj = [fOutlineView itemAtRow: selection];
-        if ([obj isKindOfClass:[HBJob class]])
-            fSavedSelectedItem = (unsigned int)[obj job] | 0x80000000;  // set high bit!
-        else
-            fSavedSelectedItem = (unsigned int)[[obj objectAtIndex:0] job];
+        if (job->sequence_id == aJobID)
+            return job;
     }
-    
+    return nil;
 }
-#endif
 
-#if HB_OUTLINE_QUEUE
+#pragma mark -
+#pragma mark Queue Counts
+
 //------------------------------------------------------------------------------------
-// Restores the expanded state of items in the outline view to match those saved by a
-// previous call to saveOutlineViewState.
+// Sets a flag indicating that the values for fPendingCount, fCompletedCount,
+// fCanceledCount, and fWorkingCount need to be recalculated.
 //------------------------------------------------------------------------------------
-- (void) restoreOutlineViewState
+- (void) setJobGroupCountsNeedUpdating: (BOOL)flag
 {
-    if (fSavedExpandedItems)
-    {
-        id anEncode;
-        NSEnumerator * e = [fEncodes objectEnumerator];
-        while ( (anEncode = [e nextObject]) )
-        {
-            hb_job_t * j = [[anEncode objectAtIndex:0] job];
-            if ([fSavedExpandedItems containsIndex: (unsigned int)j])
-                [fOutlineView expandItem: anEncode];
-        }
-    }
-    
-    if (fSavedSelectedItem)
-    {
-        // Ugh. Have to cycle through each row looking for the previously selected job.
-        // See the explanation in saveExpandedItems about the logic here.
-        
-        // Find out if the selection was a job or a group.
-        BOOL isJob = (fSavedSelectedItem & 0x80000000) == 0x80000000;
-        // Find out what hb_job_t was selected
-        hb_job_t * j = (hb_job_t *)(fSavedSelectedItem & ~0x80000000); // strip high bit
-        
-        int rowToSelect = -1;
-        for (int i = 0; i < [fOutlineView numberOfRows]; i++)
-        {
-            id obj = [fOutlineView itemAtRow: i];
-            if (isJob && [obj isKindOfClass:[HBJob class]])
-            {
-                // For a job in the outline view, test to see if it is a match
-                if ([obj job] == j)
-                {
-                    rowToSelect = i;
-                    break;
-                }
-            }
-            else if (!isJob && ![obj isKindOfClass:[HBJob class]])
-            {
-                // For a group, test to see if the group's first job is a match
-                if ([[obj objectAtIndex:0] job] == j)
-                {
-                    rowToSelect = i;
-                    break;
-                }
-            }
-        }
-        if (rowToSelect == -1)
-            [fOutlineView deselectAll: nil];
-        else
-            [fOutlineView selectRow:rowToSelect byExtendingSelection:NO];
-    }
+    fJobGroupCountsNeedUpdating = flag;
 }
-#endif
 
 //------------------------------------------------------------------------------------
-// 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
-                                          withTitle: (BOOL)withTitle
-                                         withDetail: (BOOL)withDetail
-                                   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];
-
-    static NSDictionary* detailAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
-                [NSFont systemFontOfSize:11.0], NSFontAttributeName,
-                [NSColor darkGrayColor], NSForegroundColorAttributeName,
-                ps, NSParagraphStyleAttributeName,
-                nil] retain];
-    static NSDictionary* detailHighlightedAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
-                [NSFont systemFontOfSize:11.0], NSFontAttributeName,
-                [NSColor whiteColor], NSForegroundColorAttributeName,
-                ps, NSParagraphStyleAttributeName,
-                nil] retain];
-    static NSDictionary* titleAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
-                [NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName,
-                ps, NSParagraphStyleAttributeName,
-                nil] retain];
-
-    finalString = [[[NSMutableAttributedString alloc] init] autorelease];
-
-    // 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
-    if (withTitle)
-    {
-        anAttributedString = [[[NSAttributedString alloc] initWithString:[NSString stringWithUTF8String:title->name] attributes:titleAttribute] autorelease];
-        [finalString appendAttributedString:anAttributedString];
-    }
-    
-    // Other info in plain
-    
-    aMutableString = [NSMutableString stringWithCapacity:200];
-
-    // 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 (fShowsJobsAsGroups && 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;
-    }
-*/
-
-    if (withTitle)
-    {
-        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];
-    
-        // Scan pass
-        if (job->pass == -1)
-        {
-            [aMutableString appendString:[NSString stringWithFormat:
-                    @"  (Title %d, %@, In-depth Scan)", title->index, chapterString]];
-        }
-        else
-        {
-            int numPasses = MIN( 2, job->pass + 1 );
-            if (fShowsJobsAsGroups)
-            {
-                if (numPasses == 1)
-                    [aMutableString appendString:[NSString stringWithFormat:
-                        @"  (Title %d, %@, 1 Passes)",
-                        title->index, chapterString]];
-                else
-                    [aMutableString appendString:[NSString stringWithFormat:
-                        @"  (Title %d, %@, %d Passes)",
-                        title->index, chapterString, numPasses]];
-            }
-            else
-                [aMutableString appendString:[NSString stringWithFormat:
-                        @"  (Title %d, %@, Pass %d of %d)",
-                        title->index, chapterString, MAX( 1, job->pass ), numPasses]];
-        }
-    }
-    
-    // End of title stuff
-    
-    
-    // Normal pass - show detail
-    if (withDetail && job->pass == -1)
-    {
-        [aMutableString appendString:@"Subtitle Pass"];
-    }
-    
-    else if (withDetail && job->pass != -1)
+    fPendingCount = 0;
+    fCompletedCount = 0;
+    fCanceledCount = 0;
+    fWorkingCount = 0;
+
+    NSEnumerator * groupEnum = [fJobGroups objectEnumerator];
+    HBJobGroup * aJobGroup;
+    while ( (aJobGroup = [groupEnum nextObject]) )
     {
-        NSString * jobFormat;
-        NSString * jobPictureDetail;
-        NSString * jobVideoDetail;
-        NSString * jobVideoCodec;
-        NSString * jobVideoQuality;
-        NSString * jobAudioDetail;
-        NSString * jobAudioCodec;
-
-        if ([aMutableString length] != 0)
-            [aMutableString appendString:@"\n"];
-        
-//        if (job->pass == -1)
-//            [aMutableString appendString:@"Subtitle Pass"];
-//        else
-        {
-            int passNum = MAX( 1, job->pass );
-            if (passNum == 1)
-                [aMutableString appendString:@"1st Pass"];
-            else if (passNum == 2)
-                [aMutableString appendString:@"2nd Pass"];
-            else
-                [aMutableString appendString:[NSString stringWithFormat: @"Pass %d", passNum]];
-        }
-
-        /* 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)
-        {
-            /* NTSC 29.97 */
-            jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, 29.97 fps", jobVideoCodec, jobVideoQuality];
-        }
-        else
-        {
-            /* Everything else */
-            jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, %d fps", jobVideoCodec, jobVideoQuality, job->vrate / job->vrate_base];
-        }
-        
-        /* 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];
-        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++ )
+        switch ([aJobGroup status])
         {
-            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]];
+            case HBStatusNone:
+                // We don't track these.
+                break;
+            case HBStatusPending:
+                fPendingCount++;
+                break;
+            case HBStatusCompleted:
+                fCompletedCount++;
+                break;
+            case HBStatusCanceled:
+                fCanceledCount++;
+                break;
+            case HBStatusWorking:
+                fWorkingCount++;
+                break;
         }
-        
-        /* Add the Audio detail string to the job field in the window */
-        [aMutableString appendString:[NSString stringWithFormat: @"\n%@", jobAudioDetail]];
-        
-        /*Destination Field */
-        [aMutableString appendString:[NSString stringWithFormat:@"\nDestination: %@", [NSString stringWithUTF8String:job->file]]];
     }
-    
-    anAttributedString = [[[NSAttributedString alloc] initWithString:aMutableString attributes:highlighted ? detailHighlightedAttribute : detailAttribute] autorelease];
-    [finalString appendAttributedString:anAttributedString];
-
-            
-    return finalString;
+    fJobGroupCountsNeedUpdating = NO;
 }
 
-
-
-- (NSAttributedString *)attributedDescriptionForJob: (hb_job_t *)job
-                                          withTitle: (BOOL)withTitle
-                                       withPassName: (BOOL)withPassName
-                                     withFormatInfo: (BOOL)withFormatInfo
-                                    withDestination: (BOOL)withDestination
-                                    withPictureInfo: (BOOL)withPictureInfo
-                                      withVideoInfo: (BOOL)withVideoInfo
-                                       withx264Info: (BOOL)withx264Info
-                                      withAudioInfo: (BOOL)withAudioInfo
-                                   withHighlighting: (BOOL)highlighted
+//------------------------------------------------------------------------------------
+// Returns the number of job groups whose status is HBStatusPending.
+//------------------------------------------------------------------------------------
+- (unsigned int) pendingCount
 {
-    NSMutableArray * stringParts = [NSMutableArray arrayWithCapacity:0];
-    
-    hb_title_t * title = job->title;
-    
-    // Attributes
-    static NSMutableParagraphStyle *ps = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain];
-    [ps setLineBreakMode:NSLineBreakByClipping];
-
-    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 dictionaryWithObjectsAndKeys:
-                [NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName,
-                ps, NSParagraphStyleAttributeName,
-                nil] retain];
-
-
-    // Title with summary
-    NSString * titleString;
-    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
-        titleString = [NSString stringWithUTF8String:title->name];
-
-        NSString * summaryInfo;
-    
-        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];
-
-        BOOL hasIndepthScan = (job->pass == -1);
-        int numVideoPasses = 0;
-
-        // To determine number of video passes, we need to skip past the in-depth scan.
-        if (fShowsJobsAsGroups && hasIndepthScan)
-        {
-            // 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!
-                numVideoPasses = MIN( 2, nextjob->pass + 1 );
-        }
-        else
-            numVideoPasses = MIN( 2, job->pass + 1 );
-
-        if (hasIndepthScan && numVideoPasses == 1)
-            summaryInfo = [NSString stringWithFormat: @"  (Title %d, %@, In-depth Scan, Single Video Pass)", title->index, chapterString];
-        else if (hasIndepthScan && numVideoPasses > 1)
-            summaryInfo = [NSString stringWithFormat: @"  (Title %d, %@, In-depth Scan, %d Video Passes)", title->index, chapterString, numVideoPasses];
-        else if (numVideoPasses == 1)
-            summaryInfo = [NSString stringWithFormat: @"  (Title %d, %@, Single Video Pass)", title->index, chapterString];
-        else
-            summaryInfo = [NSString stringWithFormat: @"  (Title %d, %@, %d Video Passes)", title->index, chapterString, numVideoPasses];
-
-        [stringParts addObject: [NSString stringWithFormat:@"%@%@", titleString, summaryInfo]];
-    }
-    
-    // End of title stuff
-    
-
-    // Pass Name
-    if (withPassName)
-    {
-        NSString * jobPassName;
-        if (job->pass == -1)
-            jobPassName = NSLocalizedString (@"In-depth Scan", nil);
-        else
-        {
-            int passNum = MAX( 1, job->pass );
-            if (passNum == 0)
-                jobPassName = NSLocalizedString (@"Encode 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];
-        }
-        [stringParts addObject: jobPassName];
-    }
-
-    // Video Codec needed by FormatInfo and withVideoInfo
-    NSString * jobVideoCodec;
-    if (withFormatInfo || withVideoInfo)
-    {
-        // 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 Codec needed by FormatInfo and AudioInfo
-    NSString * jobAudioCodec;
-    if (withFormatInfo || withAudioInfo)
-    {
-        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";
-    }
+    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;
+}
 
-    if (withFormatInfo)
+//------------------------------------------------------------------------------------
+// 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
+{
+    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.
+    
+    HBJobGroup * aJobGroup;
+    NSEnumerator * e = [fJobGroups objectEnumerator];
+    while ( (aJobGroup = [e nextObject]) )
     {
-        NSString * jobFormatInfo;
-        // Muxer settings (File Format in the gui)
-        if (job->mux == 65536 || job->mux == 131072 || job->mux == 1048576)
-            jobFormatInfo = @"MP4"; // HB_MUX_MP4,HB_MUX_PSP,HB_MUX_IPOD
-        else if (job->mux == 262144)
-            jobFormatInfo = @"AVI"; // HB_MUX_AVI
-        else if (job->mux == 524288)
-            jobFormatInfo = @"OGM"; // HB_MUX_OGM
-        else if (job->mux == 2097152)
-            jobFormatInfo = @"MKV"; // HB_MUX_MKV
-        else
-            jobFormatInfo = @"unknown";
-                
-        if (job->chapter_markers == 1)
-            jobFormatInfo = [NSString stringWithFormat:@"Format: %@ Container, %@ Video + %@ Audio, Chapter Markers", jobFormatInfo, jobVideoCodec, jobAudioCodec];
-        else
-            jobFormatInfo = [NSString stringWithFormat:@"Format: %@ Container, %@ Video + %@ Audio", jobFormatInfo, jobVideoCodec, jobAudioCodec];
-            
-        [stringParts addObject: jobFormatInfo];
+        if ([fOutlineView isItemExpanded: aJobGroup])
+            [fSavedExpandedItems addIndex: [aJobGroup jobAtIndex:0]->sequence_id];
     }
+    
+    // Save the selection also.
 
-    if (withDestination)
+    if (!fSavedSelectedItems)
+        fSavedSelectedItems = [[NSMutableIndexSet alloc] init];
+    else
+        [fSavedSelectedItems removeAllIndexes];
+
+    NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
+    int row = [selectedRows firstIndex];
+    while (row != NSNotFound)
     {
-        NSString * jobDestinationInfo = [NSString stringWithFormat:@"Destination: %@", [NSString stringWithUTF8String:job->file]];
-        [stringParts addObject: jobDestinationInfo];
+        aJobGroup = [fOutlineView itemAtRow: row];
+        [fSavedSelectedItems addIndex: [aJobGroup jobAtIndex:0]->sequence_id];
+        row = [selectedRows indexGreaterThanIndex: row];
     }
 
+}
 
-    if (withPictureInfo)
+//------------------------------------------------------------------------------------
+// Restores the expanded state of items in the outline view to match those saved by a
+// previous call to saveOutlineViewState.
+//------------------------------------------------------------------------------------
+- (void) restoreOutlineViewState
+{
+    if (fSavedExpandedItems)
     {
-        NSString * jobPictureInfo;
-        /*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)
+        HBJobGroup * aJobGroup;
+        NSEnumerator * e = [fJobGroups objectEnumerator];
+        while ( (aJobGroup = [e nextObject]) )
         {
-            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];
-            jobPictureInfo = [NSString stringWithFormat:@"Picture: %dx%d (%dx%d Anamorphic)", displayparwidth, displayparheight, job->width, displayparheight];
+            HBJob * job = [aJobGroup jobAtIndex:0];
+            if (job && [fSavedExpandedItems containsIndex: job->sequence_id])
+                [fOutlineView expandItem: aJobGroup];
         }
-        else
-            jobPictureInfo = [NSString stringWithFormat:@"Picture: %dx%d", job->width, job->height];
-        if (job->keep_ratio == 1)
-            jobPictureInfo = [jobPictureInfo stringByAppendingString:@" Keep Aspect Ratio"];
-        
-        if (job->grayscale == 1)
-            jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Grayscale"];
-        
-        if (job->deinterlace == 1)
-            jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Deinterlace"];
-        [stringParts addObject: jobPictureInfo];
     }
     
-    if (withVideoInfo)
+    if (fSavedSelectedItems)
     {
-        NSString * jobVideoQuality;
-        NSString * jobVideoDetail;
-        
-        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)
+        NSMutableIndexSet * rowsToSelect = [[[NSMutableIndexSet alloc] init] autorelease];
+        HBJobGroup * aJobGroup;
+        NSEnumerator * e = [fJobGroups objectEnumerator];
+        int i = 0;
+        while ( (aJobGroup = [e nextObject]) )
         {
-            /* NTSC 29.97 */
-            jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, 29.97 fps", jobVideoCodec, jobVideoQuality];
+            HBJob * job = [aJobGroup jobAtIndex:0];
+            if (job && [fSavedSelectedItems containsIndex: job->sequence_id])
+                [rowsToSelect addIndex: i];
+            i++;
         }
+        if ([rowsToSelect count] == 0)
+            [fOutlineView deselectAll: nil];
         else
-        {
-            /* Everything else */
-            jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, %d fps", jobVideoCodec, jobVideoQuality, job->vrate / job->vrate_base];
-        }
-        [stringParts addObject: jobVideoDetail];
+            [fOutlineView selectRowIndexes:rowsToSelect byExtendingSelection:NO];
     }
-    
-    if (withx264Info)
+}
+
+//------------------------------------------------------------------------------------
+// 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)
     {
-        NSString * jobx264Info;
-        if (job->x264opts)
-        {
-            jobx264Info = [NSString stringWithFormat:@"x264 Options: %@", [NSString stringWithUTF8String:job->x264opts]];
-            [stringParts addObject: jobx264Info];
-        }
+        NSRect frame = [fOutlineView frameOfCellAtColumn:col row:row];
+        [fOutlineView setNeedsDisplayInRect: frame];
     }
+}
 
-    if (withAudioInfo)
+//------------------------------------------------------------------------------------
+// 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)
     {
-        NSString * jobAudioInfo;
-        if ([jobAudioCodec isEqualToString: @"AC3"])
-            jobAudioInfo = [NSString stringWithFormat:@"Audio: %@, Pass-Through", jobAudioCodec];
-        else
-            jobAudioInfo = [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)
-                jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Mono", ai + 1]];
-            if (job->audio_mixdowns[ai] == HB_AMIXDOWN_STEREO)
-                jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Stereo", ai + 1]];
-            if (job->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBY)
-                jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Surround", ai + 1]];
-            if (job->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBYPLII)
-                jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Pro Logic II", ai + 1]];
-            if (job->audio_mixdowns[ai] == HB_AMIXDOWN_6CH)
-                jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: 6-channel discreet", ai + 1]];
-        }
-        [stringParts addObject: jobAudioInfo];
+        NSRect frame = [fOutlineView rectOfRow:row];
+        [fOutlineView setNeedsDisplayInRect: frame];
     }
-    
-    
-    NSMutableAttributedString * anAttributedString = [[[NSMutableAttributedString alloc]
-        initWithString:[stringParts componentsJoinedByString:@"\n"]
-        attributes:highlighted ? detailHighlightedAttribute : detailAttribute] autorelease];
+}
 
-    if (withTitle)
-        [anAttributedString setAttributes: titleAttribute range: NSMakeRange(0, [titleString length])];
-            
-    return anAttributedString;
+//------------------------------------------------------------------------------------
+// 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))
@@ -1128,7 +1335,7 @@ static NSString*    HBShowGroupsToolbarIdentifier             = @"HBQueueShowGro
 //------------------------------------------------------------------------------------
 // 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)
     {
@@ -1198,6 +1405,18 @@ static NSString*    HBShowGroupsToolbarIdentifier             = @"HBQueueShowGro
 }
 
 //------------------------------------------------------------------------------------
+// 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
@@ -1206,11 +1425,7 @@ static NSString*    HBShowGroupsToolbarIdentifier             = @"HBQueueShowGro
     {
         #define p s->param.working
         [fProgressBar setIndeterminate:NO];
-
-        float progress_total = fShowsJobsAsGroups ?
-                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
     }
@@ -1226,8 +1441,12 @@ static NSString*    HBShowGroupsToolbarIdentifier             = @"HBQueueShowGro
     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
 }
 
 //------------------------------------------------------------------------------------
@@ -1236,275 +1455,382 @@ static NSString*    HBShowGroupsToolbarIdentifier             = @"HBQueueShowGro
 - (void)updateQueueCountField
 {
     NSString * msg;
-    int jobCount;
-    
-    if (fShowsJobsAsGroups)
+    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
     {
-        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
-    {
-        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];
-
+            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)
         {
-            switch (job->pass)
-            {
-                case -1:  // in-depth scan
-                    [fJobDescTextField setAttributedStringValue:
-                        [self attributedDescriptionForJob:job
-                                    withTitle: YES
-                                 withPassName: YES
-                               withFormatInfo: NO
-                              withDestination: NO
-                              withPictureInfo: NO
-                                withVideoInfo: NO
-                                 withx264Info: NO
-                                withAudioInfo: NO
-                             withHighlighting: NO]];
-                    break;
-                    
-                case 1:  // video 1st pass
-                    [fJobDescTextField setAttributedStringValue:
-                        [self attributedDescriptionForJob:job
-                                    withTitle: YES
-                                 withPassName: YES
-                               withFormatInfo: NO
-                              withDestination: NO
-                              withPictureInfo: YES
-                                withVideoInfo: YES
-                                 withx264Info: YES
-                                withAudioInfo: NO
-                             withHighlighting: NO]];
-                    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;
                 
-                case 0:  // single pass
-                case 2:  // video 2nd pass + audio
-                    [fJobDescTextField setAttributedStringValue:
-                        [self attributedDescriptionForJob:job
-                                    withTitle: YES
-                                 withPassName: YES
-                               withFormatInfo: NO
-                              withDestination: NO
-                              withPictureInfo: YES
-                                withVideoInfo: YES
-                                 withx264Info: YES
-                                withAudioInfo: YES
-                             withHighlighting: NO]];
-                    break;
-                
-                default: // unknown
-                    [fJobDescTextField setAttributedStringValue:
-                        [self attributedDescriptionForJob:job
-                                    withTitle: YES
-                                 withPassName: YES
-                               withFormatInfo: NO
-                              withDestination: NO
-                              withPictureInfo: YES
-                                withVideoInfo: YES
-                                 withx264Info: YES
-                                withAudioInfo: YES
-                             withHighlighting: NO]];
-            }
-
-            [self showCurrentJobPane:YES];
-            [fJobIconView setImage: fShowsJobsAsGroups ? [NSImage imageNamed:@"JobLarge"] : [self largeImageForPass: job->pass] ];
+            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
 {
-#if HB_OUTLINE_QUEUE
     [self saveOutlineViewState];
-    [self rebuildEncodes];
+}
+
+//------------------------------------------------------------------------------------
+// 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];
-#else
-    [fTaskView noteNumberOfRowsChanged];
-    [fTaskView reloadData];
-#endif
-    
+    [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--;
-            if (fShowsJobsAsGroups)
-                hb_rem_group( fHandle, hb_group( fHandle, row ) );
-            else
-                hb_rem( fHandle, hb_job( fHandle, row ) );
+            HBJobGroup * jobGroup = [fOutlineView itemAtRow: row];
+            if ([[jobGroup destinationPath] length])
+                [[NSWorkspace sharedWorkspace] selectFile:[jobGroup destinationPath] inFileViewerRootedAtPath:nil];
+        
+            row = [selectedRows indexGreaterThanIndex: row];
         }
-#else
-  #if HB_OUTLINE_QUEUE
-        hb_job_t * job = [[[fOutlineView itemAtRow: row] objectAtIndex: 0] job];
-        hb_rem_group( fHandle, job );
-  #else
-        if (fShowsJobsAsGroups)
-            hb_rem_group( fHandle, hb_group( fHandle, row ) );
-        else
-            hb_rem( fHandle, hb_job( fHandle, row ) );
-  #endif
-#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
 {
     [fHBController Cancel:sender];
 }
 
-//------------------------------------------------------------------------------------
-// Turns on the display of detail information for each job. Does nothing if detail is
-// already turned on.
-//------------------------------------------------------------------------------------
-- (IBAction)showDetail: (id)sender
-{
-    if (!fShowsDetail)
-        [self setShowsDetail:YES];
+//------------------------------------------------------------------------------------
+// Starts or cancels the processing of jobs depending on the current state
+//------------------------------------------------------------------------------------
+- (IBAction)toggleStartCancel: (id)sender
+{
+    if (!fHandle) return;
+    
+    hb_state_t s;
+    hb_get_state2 (fHandle, &s);
+
+    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 ([self pendingCount] > 0)
+        [fHBController doRip];
+}
+
+//------------------------------------------------------------------------------------
+// Toggles the pause/resume state of libhb
+//------------------------------------------------------------------------------------
+- (IBAction)togglePauseResume: (id)sender
+{
+    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);
+}
+
+#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];
+    }
+
 }
 
 //------------------------------------------------------------------------------------
-// Turns off the display of detail information for each job. Does nothing if detail is
-// already turned off.
+// 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)hideDetail: (id)sender
+- (void)libhbWillStop
 {
-    if (fShowsDetail)
-        [self setShowsDetail:NO];
+    if (fCurrentJobGroup)
+        [fCurrentJobGroup setStatus: HBStatusCanceled];
 }
 
 //------------------------------------------------------------------------------------
-// Turns on displaying of jobs as groups by calling setShowsJobsAsGroups:YES. Does
-// nothing if groups are already turned on. 
+// Notifies HBQueueController that libhb's state has changed
 //------------------------------------------------------------------------------------
-- (IBAction)showJobsAsGroups: (id)sender
+- (void)libhbStateChanged: (hb_state_t &)state
 {
-    if (!fShowsJobsAsGroups)
-        [self setShowsJobsAsGroups:YES];
-}
+    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];
+            }
 
-//------------------------------------------------------------------------------------
-// Turns on displaying of jobs as individual items by calling setShowsJobsAsGroups:NO.
-// Does nothing if groups are already turned off. 
-//------------------------------------------------------------------------------------
-- (IBAction)showJobsAsPasses: (id)sender
-{
-    if (fShowsJobsAsGroups)
-        [self setShowsJobsAsGroups:NO];
-}
+            if (fCurrentJob)
+            {
+                [self updateCurrentJobProgress];
+                [self startAnimatingCurrentJobGroupInQueue];
+            }
+            break;
+        }
 
-//------------------------------------------------------------------------------------
-// Starts or cancels the processing of jobs depending on the current state
-//------------------------------------------------------------------------------------
-- (IBAction)toggleStartCancel: (id)sender
-{
-    if (!fHandle) return;
-    
-    hb_state_t s;
-    hb_get_state2 (fHandle, &s);
+        case HB_STATE_MUXING:
+        {
+            [self updateCurrentJobProgress];
+            break;
+        }
 
-    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
+        case HB_STATE_PAUSED:
+        {
+            [self updateCurrentJobProgress];
+            [self stopAnimatingCurrentJobGroupInQueue];
+            break;
+        }
 
-    else
-    {
-        if (fShowsJobsAsGroups)
+        case HB_STATE_WORKDONE:
         {
-            if (hb_group_count(fHandle) > 0)
-                [fHBController doRip];
+            // 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;
         }
-        else if (hb_count(fHandle) > 0)
-            [fHBController doRip];
-    }    
-}
 
-//------------------------------------------------------------------------------------
-// Toggles the pause/resume state of hblib
-//------------------------------------------------------------------------------------
-- (IBAction)togglePauseResume: (id)sender
-{
-    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);
 }
 
 #if HB_OUTLINE_METRIC_CONTROLS
@@ -1521,6 +1847,21 @@ static float spacingWidth = 3.0;
 }
 #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
@@ -1561,143 +1902,36 @@ static float spacingWidth = 3.0;
     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:)];
-       }
-    
-#if !HB_OUTLINE_QUEUE
-    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: @"Detail"];
-               [toolbarItem setPaletteLabel: @"Detail"];
-               
-               // Set up a reasonable tooltip, and image
-               [toolbarItem setToolTip: @"Displays detailed information in the queue"];
-               [toolbarItem setImage: [NSImage imageNamed: @"Detail"]];
-               
-               // Tell the item what message to send when it is clicked 
-               [toolbarItem setTarget: self];
-               [toolbarItem setAction: fShowsDetail ? @selector(hideDetail:) : @selector(showDetail:)];
-       }
-    
-    else if ([itemIdentifier isEqual: HBShowGroupsToolbarIdentifier])
-    {
-        toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
-               
-/*
+        
         // Set the text label to be displayed in the toolbar and customization palette 
-               [toolbarItem setLabel: @"Passes"];
-               [toolbarItem setPaletteLabel: @"Passes"];
-               
-               // Set up a reasonable tooltip, and image
-               [toolbarItem setToolTip: @"Displays individual passes in the queue"];
-               [toolbarItem setImage: [NSImage imageNamed: @"Passes"]];
+        [toolbarItem setLabel: @"Pause"];
+        [toolbarItem setPaletteLabel: @"Pause/Resume"];
         
-               // Tell the item what message to send when it is clicked 
-               [toolbarItem setTarget: self];
-               [toolbarItem setAction: fShowsJobsAsGroups ? @selector(showJobsAsPasses:) : @selector(showJobsAsGroups:)];
-*/
-  
-// Various attempts at other button types in the toolbar. A matrix worked fine to display
-// a button for encodes & passes, but ultimately I decided to go with a single button
-// called "Passes" that toggles on or off. All these suffer from the fact taht you need
-// to override NSToolbarItem for them in order to validate their state.
-               [toolbarItem setLabel: @"View"];
-               [toolbarItem setPaletteLabel: @"View"];
-
-        NSButtonCell * buttonCell = [[[NSButtonCell alloc] initImageCell:nil] autorelease];
-        [buttonCell setBezelStyle: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 setAction: @selector(showJobsAsGroups:)];
-               [matrix setToolTip: @"Displays encodes in the queue" forCell:buttonCell];
-
-        buttonCell = [matrix cellAtRow:0 column:1];
-        [buttonCell setTitle:@""];
-        [buttonCell setImage:[NSImage imageNamed: @"Passes"]];
-        [buttonCell setAlternateImage:[NSImage imageNamed: @"PassesPressed"]];
-               [buttonCell setAction: @selector(showJobsAsPasses:)];
-               [matrix setToolTip: @"Displays individual passes in the queue" forCell:buttonCell];
-
-        [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,32,32)] 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:)];
-*/
-       }
-#endif
+        // 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;
 }
@@ -1713,11 +1947,6 @@ static float spacingWidth = 3.0;
     return [NSArray arrayWithObjects:
         HBQueueStartCancelToolbarIdentifier,
         HBQueuePauseResumeToolbarIdentifier,
-#if !HB_OUTLINE_QUEUE
-               NSToolbarSeparatorItemIdentifier,
-               HBShowGroupsToolbarIdentifier,
-        HBShowDetailToolbarIdentifier,
-#endif
         nil];
 }
 
@@ -1733,14 +1962,10 @@ static float spacingWidth = 3.0;
     return [NSArray arrayWithObjects:
         HBQueueStartCancelToolbarIdentifier,
         HBQueuePauseResumeToolbarIdentifier,
-#if !HB_OUTLINE_QUEUE
-               HBShowGroupsToolbarIdentifier,
-        HBShowDetailToolbarIdentifier,
-#endif
-               NSToolbarCustomizeToolbarItemIdentifier,
-               NSToolbarFlexibleSpaceItemIdentifier,
+        NSToolbarCustomizeToolbarItemIdentifier,
+        NSToolbarFlexibleSpaceItemIdentifier,
         NSToolbarSpaceItemIdentifier,
-               NSToolbarSeparatorItemIdentifier,
+        NSToolbarSeparatorItemIdentifier,
         nil];
 }
 
@@ -1765,26 +1990,26 @@ static float spacingWidth = 3.0;
         {
             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])
     {
@@ -1792,61 +2017,27 @@ static float spacingWidth = 3.0;
         {
             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"];
-        }
-       }
-    
-#if !HB_OUTLINE_QUEUE
-    else if ([[toolbarItem itemIdentifier] isEqual: HBShowGroupsToolbarIdentifier])
-    {
-        enable = hb_count(fHandle) > 0;
-               [toolbarItem setAction: fShowsJobsAsGroups ? @selector(showJobsAsPasses:) : @selector(showJobsAsGroups:)];
-        if (fShowsJobsAsGroups)
-        {
-            [toolbarItem setImage: [NSImage imageNamed: @"Passes"]];
-            [toolbarItem setToolTip: @"Displays individual passes in the queue"];
-        }
-        else
-        {
-            [toolbarItem setImage: [NSImage imageNamed: @"PassesPressed"]];
-            [toolbarItem setToolTip: @"Displays encodes in the queue"];
+            [toolbarItem setLabel: @"Pause"];
+            [toolbarItem setToolTip: @"Pause Encoding"];
         }
     }
     
-    else if ([[toolbarItem itemIdentifier] isEqual: HBShowDetailToolbarIdentifier])
-    {
-        enable = hb_count(fHandle) > 0;
-               [toolbarItem setAction: fShowsDetail ? @selector(hideDetail:) : @selector(showDetail:)];
-        if (fShowsDetail)
-        {
-            [toolbarItem setImage: [NSImage imageNamed: @"DetailPressed"]];
-            [toolbarItem setToolTip: @"Hides detailed information in the queue"];
-        }
-        else
-        {
-            [toolbarItem setImage: [NSImage imageNamed: @"Detail"]];
-            [toolbarItem setToolTip: @"Displays detailed information in the queue"];
-        }
-    }
-#endif
-
-       return enable;
+    return enable;
 }
 
 #pragma mark -
@@ -1863,26 +2054,16 @@ static float spacingWidth = 3.0;
     [fQueueWindow setFrameAutosaveName: @"Queue"];
     [fQueueWindow setExcludedFromWindowsMenu:YES];
 
-#if HB_OUTLINE_QUEUE
-    [[fOutlineView enclosingScrollView] setHidden: NO];
-#else
-    [[fTaskView enclosingScrollView] setHidden: NO];
-#endif
-
 #if HB_QUEUE_DRAGGING
-  #if HB_OUTLINE_QUEUE
-    [fOutlineView registerForDraggedTypes: [NSArray arrayWithObject:HBQueueDataType] ];
-  #else
-    [fTaskView registerForDraggedTypes: [NSArray arrayWithObject:HBQueueDataType] ];
-  #endif
+    [fOutlineView registerForDraggedTypes: [NSArray arrayWithObject:HBQueuePboardType] ];
+    [fOutlineView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES];
+    [fOutlineView setVerticalMotionCanBeginDrag: YES];
 #endif
 
-#if HB_OUTLINE_QUEUE
     // Don't allow autoresizing of main column, else the "delete" column will get
     // pushed out of view.
     [fOutlineView setAutoresizesOutlineColumn: NO];
-    [fOutlineView setIndentationPerLevel:21];
-#endif
+
 #if HB_OUTLINE_METRIC_CONTROLS
     [fIndentation setHidden: NO];
     [fSpacing setHidden: NO];
@@ -1891,9 +2072,10 @@ static float spacingWidth = 3.0;
 #endif
 
     // Show/hide UI elements
-    [self setShowsDetail:fShowsDetail];
-    [self setShowsJobsAsGroups:fShowsJobsAsGroups];
+    fCurrentJobPaneShown = YES;     // it's shown in the nib
     [self showCurrentJobPane:NO];
+
+    [self updateQueueCountField];
 }
 
 
@@ -1906,299 +2088,147 @@ static float spacingWidth = 3.0;
 }
 
 #pragma mark -
-#pragma mark NSTableView delegate
-
-//------------------------------------------------------------------------------------
-// NSTableView delegate
-//------------------------------------------------------------------------------------
-- (int)numberOfRowsInTableView: (NSTableView *)aTableView
-{
-#if HB_UNI_QUEUE
-    int numItems = hb_current_job(fHandle) ? 1 : 0;
-    if (fShowsJobsAsGroups)
-        return numItems + hb_group_count(fHandle);
-    else
-        return numItems + hb_count(fHandle);
-#else
-    if (fShowsJobsAsGroups)
-        return hb_group_count(fHandle);
-    else
-        return hb_count(fHandle);
-#endif
-}
 
-//------------------------------------------------------------------------------------
-// NSTableView delegate
-//------------------------------------------------------------------------------------
-- (id)tableView: (NSTableView *)aTableView
-      objectValueForTableColumn: (NSTableColumn *)aTableColumn
-                            row: (int)rowIndex
+- (void)moveObjectsInArray:(NSMutableArray *)array fromIndexes:(NSIndexSet *)indexSet toIndex:(unsigned)insertIndex
 {
-    if (!fHandle)
-        return @"";    // fatal error!
-        
-    hb_job_t * job = nil;
-
-#if HB_UNI_QUEUE
-    // Looking for the current job?
-    int jobIndex = rowIndex;
-    if (hb_current_job(fHandle))
-    {
-        if (rowIndex == 0)
-            job = hb_current_job(fHandle);
-        else
-            jobIndex = rowIndex - 1;
-    }
-    
-    if (!job)
-    {
-        if (fShowsJobsAsGroups)
-            job = hb_group(fHandle, jobIndex);
-        else
-            job = hb_job(fHandle, jobIndex);
-    }
-#else
-    if (fShowsJobsAsGroups)
-        job = hb_group(fHandle, rowIndex);
-    else
-        job = hb_job(fHandle, rowIndex);
-#endif
+    unsigned index = [indexSet lastIndex];
+    unsigned aboveInsertIndexCount = 0;
     
-    if (!job)
-        return @"";    // fatal error!
-
-    if ([[aTableColumn identifier] isEqualToString:@"desc"])
-    {
-        BOOL highlighted = [aTableView isRowSelected:rowIndex] && [[aTableView window] isKeyWindow] && ([[aTableView window] firstResponder] == aTableView);
-        return [self attributedDescriptionForJob:job withTitle:YES withDetail:fShowsDetail withHighlighting:highlighted];    
-    }
-    
-    else if ([[aTableColumn identifier] isEqualToString:@"delete"])
-        return @"";
-
-    else if ([[aTableColumn identifier] isEqualToString:@"icon"])
-        return fShowsJobsAsGroups ? [NSImage imageNamed:@"JobSmall"] : [self smallImageForPass: job->pass];
-
-    return @"";
-}
-
-//------------------------------------------------------------------------------------
-// NSTableView delegate
-//------------------------------------------------------------------------------------
-- (void)tableView: (NSTableView *)aTableView
-        willDisplayCell: (id)aCell
-         forTableColumn: (NSTableColumn *)aTableColumn
-                    row: (int)rowIndex
-{
-    if ([[aTableColumn identifier] isEqualToString:@"delete"])
+    while (index != NSNotFound)
     {
-        BOOL highlighted = [aTableView isRowSelected:rowIndex] && [[aTableView window] isKeyWindow] && ([[aTableView window] firstResponder] == aTableView);
-        if (highlighted)
+        unsigned removeIndex;
+        
+        if (index >= insertIndex)
         {
-            [aCell setImage:[NSImage imageNamed:@"DeleteHighlight"]];
-            [aCell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]];
+            removeIndex = index + aboveInsertIndexCount;
+            aboveInsertIndexCount++;
         }
         else
         {
-            [aCell setImage:[NSImage imageNamed:@"Delete"]];
+            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
-//------------------------------------------------------------------------------------
-#if HB_UNI_QUEUE
-- (float)tableView:(NSTableView *)tableView heightOfRow:(int)row
+#pragma mark -
+#pragma mark NSOutlineView delegate
+
+- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
 {
-    if ((row == 0) && hb_current_job(fHandle))
-        return HB_ROW_HEIGHT_ACTIVE_JOB;
-    else 
-        return fShowsDetail ? HB_ROW_HEIGHT_DETAIL : HB_ROW_HEIGHT_NO_DETAIL;
+    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;
 }
-#endif
 
-#if HB_QUEUE_DRAGGING
-- (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard
+- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
 {
-    // Copy the row numbers to the pasteboard.
-    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
-    [pboard declareTypes:[NSArray arrayWithObject:HBQueueDataType] owner:self];
-    [pboard setData:data forType:HBQueueDataType];
+    // 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;
 }
-#endif
 
-#if HB_QUEUE_DRAGGING
-- (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)op
+- (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item
 {
-    // Add code here to validate the drop
-    NSLog(@"validate Drop");
-    return NSDragOperationEvery;
-}
-#endif
-
+    // 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
-- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id <NSDraggingInfo>)info
-            row:(int)row dropOperation:(NSTableViewDropOperation)operation
-{
-    NSPasteboard* pboard = [info draggingPasteboard];
-    NSData* rowData = [pboard dataForType:HBQueueDataType];
-    NSIndexSet* rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData];
-    int dragRow = [rowIndexes firstIndex];
-    // Move the specified row to its new location...
-    
-    return YES;
-}
+       // Don't autoexpand while dragging, since we can't drop into the items
+       return ![(HBQueueOutlineView*)outlineView isDragging];
+#else
+       return YES;
 #endif
+}
 
-#pragma mark -
-#pragma mark NSOutlineView delegate
-
-#if HB_OUTLINE_QUEUE
-
-- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
+- (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 [fEncodes objectAtIndex:index];
+        return [fJobGroups count];
     else
-        return [item objectAtIndex:index];
+        return 0;
 }
 
-- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
+- (void)outlineViewItemDidCollapse:(NSNotification *)notification
 {
-    return ! [item isKindOfClass:[HBJob class]];
+    id item = [[notification userInfo] objectForKey:@"NSObject"];
+    int row = [fOutlineView rowForItem:item];
+    [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
 }
 
-- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
+- (void)outlineViewItemDidExpand:(NSNotification *)notification
 {
-    if (item == nil)
-        return [fEncodes count];
-    else
-        return [item count];
+    id item = [[notification userInfo] objectForKey:@"NSObject"];
+    int row = [fOutlineView rowForItem:item];
+    [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
 }
 
 - (float)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
 {
-/*
-    if (fShowsDetail && [item isKindOfClass:[HBJob class]] && ([item job]->pass != -1))
-        return HB_ROW_HEIGHT_DETAIL;
-    else
-        return HB_ROW_HEIGHT_NO_DETAIL;
-*/
-
-    if (fShowsDetail && [item isKindOfClass:[HBJob class]])
+    if ([outlineView isItemExpanded: item])
     {
-        hb_job_t * j = [item job];
-        NSAssert (j != nil, @"job is nil");
-        if (j->pass == -1)
-            return HB_ROW_HEIGHT_INDEPTH_PASS;
-        else if (j->pass == 1)
-            return HB_ROW_HEIGHT_1ST_PASS;
-        else if ((j->pass == 2) || (j->pass == 0))
-            return HB_ROW_HEIGHT_2ND_PASS;
-        else
-            return HB_ROW_HEIGHT_TITLE_ONLY;    // unknown!
+        // 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 ([outlineView isItemExpanded: item])
-            return HB_ROW_HEIGHT_TITLE_WITH_SUMMARY;
-        else
-            return HB_ROW_HEIGHT_TITLE_ONLY;
-    }
+        return HB_ROW_HEIGHT_TITLE_ONLY;
 }
 
 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
 {
-    BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView);
-    if ([item isKindOfClass:[HBJob class]])
+       // 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"])
     {
-        if ([[tableColumn identifier] isEqualToString:@"desc"])
+        switch ([(HBJobGroup*)item status])
         {
-            hb_job_t * job = [item job];
-            switch (job->pass)
-            {
-                case -1:  // in-depth scan
-                    return [self attributedDescriptionForJob:job
-                                    withTitle: NO
-                                 withPassName: YES
-                               withFormatInfo: NO
-                              withDestination: NO
-                              withPictureInfo: NO
-                                withVideoInfo: NO
-                                 withx264Info: NO
-                                withAudioInfo: NO
-                             withHighlighting: highlighted];
-                    break;
-                
-                case 1:  // video 1st pass
-                    return [self attributedDescriptionForJob:job
-                                    withTitle: NO
-                                 withPassName: YES
-                               withFormatInfo: NO
-                              withDestination: NO
-                              withPictureInfo: YES
-                                withVideoInfo: YES
-                                 withx264Info: YES
-                                withAudioInfo: NO
-                             withHighlighting: highlighted];
-                    break;
-                
-                case 0:  // single pass
-                case 2:  // video 2nd pass + audio
-                    return [self attributedDescriptionForJob:job
-                                    withTitle: NO
-                                 withPassName: YES
-                               withFormatInfo: NO
-                              withDestination: NO
-                              withPictureInfo: YES
-                                withVideoInfo: YES
-                                 withx264Info: YES
-                                withAudioInfo: YES
-                             withHighlighting: highlighted];
-                    break;
-                
-                default:
-                    return @"unknown";
-            }
-            
+            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
-    {
-        hb_job_t * job = [[item objectAtIndex:0] job];
-        if ([[tableColumn identifier] isEqualToString:@"desc"])
-        {
-            if ([fOutlineView isItemExpanded: item])
-                return [self attributedDescriptionForJob:job
-                                        withTitle: YES
-                                     withPassName: NO
-                                   withFormatInfo: YES
-                                  withDestination: YES
-                                  withPictureInfo: NO
-                                    withVideoInfo: NO
-                                     withx264Info: NO
-                                    withAudioInfo: NO
-                                 withHighlighting: highlighted];
-            else
-                return [self attributedDescriptionForJob:job
-                                        withTitle: YES
-                                     withPassName: NO
-                                   withFormatInfo: NO
-                                  withDestination: NO
-                                  withPictureInfo: NO
-                                    withVideoInfo: NO
-                                     withx264Info: NO
-                                    withAudioInfo: NO
-                                 withHighlighting: highlighted];
-        }
-        
-    }
-
-    return @"";
+        return @"";
 }
 
 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
@@ -2211,25 +2241,31 @@ static float spacingWidth = 3.0;
         [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
-        if ([item isKindOfClass:[HBJob class]])
-            [cell setImage:[self smallImageForPass: [item job]->pass]];
-        else
-            [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.
-        if ([item isKindOfClass:[HBJob class]])
+        [cell setEnabled: YES];
+        BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView);
+        if ([(HBJobGroup*)item status] == HBStatusCompleted)
         {
-            [cell setEnabled: NO];
-            [cell setImage: nil];
+            [cell setAction: @selector(revealSelectedJobGroups:)];
+            if (highlighted)
+            {
+                [cell setImage:[NSImage imageNamed:@"RevealHighlight"]];
+                [cell setAlternateImage:[NSImage imageNamed:@"RevealHighlightPressed"]];
+            }
+            else
+                [cell setImage:[NSImage imageNamed:@"Reveal"]];
         }
         else
         {
-            [cell setEnabled: YES];
-            BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView);
+            [cell setAction: @selector(removeSelectedJobGroups:)];
             if (highlighted)
             {
                 [cell setImage:[NSImage imageNamed:@"DeleteHighlight"]];
@@ -2251,6 +2287,86 @@ static float spacingWidth = 3.0;
         [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