1 /* $Id: PictureController.mm,v 1.11 2005/08/01 15:10:44 titer Exp $
3 This file is part of the HandBrake source code.
4 Homepage: <http://handbrake.fr/>.
5 It may be used under the terms of the GNU General Public License. */
7 #import "PictureController.h"
9 @interface PictureController (Private)
11 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize;
12 - (void)resizeSheetForViewSize: (NSSize)viewSize;
13 - (void)setViewSize: (NSSize)viewSize;
14 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize;
18 @implementation PictureController
20 - (id)initWithDelegate:(id)del
22 if (self = [super initWithWindowNibName:@"PictureSettings"])
24 // NSWindowController likes to lazily load its window. However since
25 // this controller tries to set all sorts of outlets before the window
26 // is displayed, we need it to load immediately. The correct way to do
27 // this, according to the documentation, is simply to invoke the window
30 // If/when we switch a lot of this stuff to bindings, this can probably
35 fPicturePreviews = [[NSMutableDictionary dictionaryWithCapacity: HB_NUM_HBLIB_PICTURES] retain];
42 [fPicturePreviews release];
46 - (void) SetHandle: (hb_handle_t *) handle
50 [fWidthStepper setValueWraps: NO];
51 [fWidthStepper setIncrement: 16];
52 [fWidthStepper setMinValue: 64];
53 [fHeightStepper setValueWraps: NO];
54 [fHeightStepper setIncrement: 16];
55 [fHeightStepper setMinValue: 64];
57 [fCropTopStepper setIncrement: 2];
58 [fCropTopStepper setMinValue: 0];
59 [fCropBottomStepper setIncrement: 2];
60 [fCropBottomStepper setMinValue: 0];
61 [fCropLeftStepper setIncrement: 2];
62 [fCropLeftStepper setMinValue: 0];
63 [fCropRightStepper setIncrement: 2];
64 [fCropRightStepper setMinValue: 0];
67 - (void) SetTitle: (hb_title_t *) title
69 hb_job_t * job = title->job;
73 [fWidthStepper setMaxValue: title->width];
74 [fWidthStepper setIntValue: job->width];
75 [fWidthField setIntValue: job->width];
76 [fHeightStepper setMaxValue: title->height];
77 [fHeightStepper setIntValue: job->height];
78 [fHeightField setIntValue: job->height];
79 [fRatioCheck setState: job->keep_ratio ? NSOnState : NSOffState];
80 [fCropTopStepper setMaxValue: title->height/2-2];
81 [fCropBottomStepper setMaxValue: title->height/2-2];
82 [fCropLeftStepper setMaxValue: title->width/2-2];
83 [fCropRightStepper setMaxValue: title->width/2-2];
85 /* Populate the Anamorphic NSPopUp button here */
86 [fAnamorphicPopUp removeAllItems];
87 [fAnamorphicPopUp addItemWithTitle: @"None"];
88 [fAnamorphicPopUp addItemWithTitle: @"Strict"];
89 if (allowLooseAnamorphic)
91 [fAnamorphicPopUp addItemWithTitle: @"Loose"];
93 [fAnamorphicPopUp selectItemAtIndex: job->pixel_ratio];
95 /* We initially set the previous state of keep ar to on */
96 keepAspectRatioPreviousState = 1;
99 [fCropMatrix selectCellAtRow: 1 column:0];
100 /* If auto, lets set the crop steppers according to current job->crop values */
101 [fCropTopStepper setIntValue: job->crop[0]];
102 [fCropTopField setIntValue: job->crop[0]];
103 [fCropBottomStepper setIntValue: job->crop[1]];
104 [fCropBottomField setIntValue: job->crop[1]];
105 [fCropLeftStepper setIntValue: job->crop[2]];
106 [fCropLeftField setIntValue: job->crop[2]];
107 [fCropRightStepper setIntValue: job->crop[3]];
108 [fCropRightField setIntValue: job->crop[3]];
112 [fCropMatrix selectCellAtRow: 0 column:0];
115 /* Set filters widgets according to the filters struct */
116 [fDetelecineCheck setState:fPictureFilterSettings.detelecine];
117 [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
118 [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise];
119 [fDeblockCheck setState: fPictureFilterSettings.deblock];
122 MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
123 MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
124 [self SettingsChanged: nil];
127 /* we use this to setup the initial picture filters upon first launch, after that their states
128 are maintained across different sources */
129 - (void) setInitialPictureFilters
131 /* we use a popup to show the deinterlace settings */
132 [fDeinterlacePopUp removeAllItems];
133 [fDeinterlacePopUp addItemWithTitle: @"None"];
134 [fDeinterlacePopUp addItemWithTitle: @"Fast"];
135 [fDeinterlacePopUp addItemWithTitle: @"Slow"];
136 [fDeinterlacePopUp addItemWithTitle: @"Slower"];
138 /* Set deinterlaces level according to the integer in the main window */
139 [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
141 /* we use a popup to show the denoise settings */
142 [fDenoisePopUp removeAllItems];
143 [fDenoisePopUp addItemWithTitle: @"None"];
144 [fDenoisePopUp addItemWithTitle: @"Weak"];
145 [fDenoisePopUp addItemWithTitle: @"Medium"];
146 [fDenoisePopUp addItemWithTitle: @"Strong"];
147 /* Set denoises level according to the integer in the main window */
148 [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise];
150 /* we use a popup to show the decomb settings */
151 [fDecombPopUp removeAllItems];
152 [fDecombPopUp addItemWithTitle: @"None"];
153 [fDecombPopUp addItemWithTitle: @"Default"];
154 [fDecombPopUp addItemWithTitle: @"Custom"];
155 /* Set denoises level according to the integer in the main window */
156 [fDecombPopUp selectItemAtIndex: fPictureFilterSettings.decomb];
160 // Adjusts the window to draw the current picture (fPicture) adjusting its size as
161 // necessary to display as much of the picture as possible.
162 - (void) displayPreview
164 [fPictureView setImage: [self imageForPicture: fPicture]];
166 NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
167 /* Set the picture size display fields below the Preview Picture*/
168 if( fTitle->job->pixel_ratio == 1 ) // Original PAR Implementation
170 output_width = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3];
171 output_height = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1];
172 display_width = output_width * fTitle->job->pixel_aspect_width / fTitle->job->pixel_aspect_height;
173 [fInfoField setStringValue:[NSString stringWithFormat:
174 @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
175 fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
176 displaySize.width *= ( ( CGFloat )fTitle->job->pixel_aspect_width ) / ( ( CGFloat )fTitle->job->pixel_aspect_height );
178 else if (fTitle->job->pixel_ratio == 2) // Loose Anamorphic
180 display_width = output_width * output_par_width / output_par_height;
181 [fInfoField setStringValue:[NSString stringWithFormat:
182 @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
183 fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
185 /* FIXME: use the original aspect ratio to calculate the displaySize,
186 probably the size will not be the right one,
187 but at least the windows does not resize every time. */
188 displaySize.width *= ( ( CGFloat )fTitle->job->pixel_aspect_width) / ( ( CGFloat )fTitle->job->pixel_aspect_height );
190 else // No Anamorphic
192 [fInfoField setStringValue: [NSString stringWithFormat:
193 @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
194 fTitle->job->width, fTitle->job->height]];
197 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
198 if( [self viewNeedsToResizeToSize:viewSize] )
200 [self resizeSheetForViewSize:viewSize];
201 [self setViewSize:viewSize];
204 // Show the scaled text (use the height to check since the width can vary
205 // with anamorphic video).
206 if( ( ( int )viewSize.height ) != fTitle->height )
208 CGFloat scale = viewSize.width / ( ( CGFloat ) fTitle->width );
209 NSString *scaleString = [NSString stringWithFormat:
210 NSLocalizedString( @" (Preview scaled to %.0f%% actual size)",
211 @"String shown when a preview is scaled" ),
213 [fInfoField setStringValue: [[fInfoField stringValue] stringByAppendingString:scaleString]];
216 [fPrevButton setEnabled: ( fPicture > 0 )];
217 [fNextButton setEnabled: ( fPicture < 9 )];
220 - (IBAction) deblockSliderChanged: (id) sender
222 if ([fDeblockSlider floatValue] == 4.0)
224 [fDeblockField setStringValue: [NSString stringWithFormat: @"Off"]];
228 [fDeblockField setStringValue: [NSString stringWithFormat: @"%.0f", [fDeblockSlider floatValue]]];
230 [self SettingsChanged: sender];
233 - (IBAction) SettingsChanged: (id) sender
235 hb_job_t * job = fTitle->job;
237 autoCrop = ( [fCropMatrix selectedRow] == 0 );
238 [fCropTopStepper setEnabled: !autoCrop];
239 [fCropBottomStepper setEnabled: !autoCrop];
240 [fCropLeftStepper setEnabled: !autoCrop];
241 [fCropRightStepper setEnabled: !autoCrop];
245 memcpy( job->crop, fTitle->crop, 4 * sizeof( int ) );
249 job->crop[0] = [fCropTopStepper intValue];
250 job->crop[1] = [fCropBottomStepper intValue];
251 job->crop[2] = [fCropLeftStepper intValue];
252 job->crop[3] = [fCropRightStepper intValue];
255 if( [fAnamorphicPopUp indexOfSelectedItem] > 0 )
257 if ([fAnamorphicPopUp indexOfSelectedItem] == 2) // Loose anamorphic
259 job->pixel_ratio = 2;
260 [fWidthStepper setEnabled: YES];
261 [fWidthField setEnabled: YES];
262 /* We set job->width and call hb_set_anamorphic_size in libhb to do a "dry run" to get
263 * the values to be used by libhb for loose anamorphic
265 /* if the sender is the anamorphic popup, then we know that loose anamorphic has just
266 * been turned on, so snap the width to full width for the source.
268 if (sender == fAnamorphicPopUp)
270 [fWidthStepper setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
271 [fWidthField setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
273 job->width = [fWidthStepper intValue];
274 hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
275 [fHeightStepper setIntValue: output_height];
276 [fHeightField setIntValue: output_height];
277 job->height = [fHeightStepper intValue];
280 else // must be "1" or strict anamorphic
282 [fWidthStepper setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
283 [fWidthField setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
285 /* This will show correct anamorphic height values, but
286 show distorted preview picture ratio */
287 [fHeightStepper setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
288 [fHeightField setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
289 job->width = [fWidthStepper intValue];
290 job->height = [fHeightStepper intValue];
292 job->pixel_ratio = 1;
293 [fWidthStepper setEnabled: NO];
294 [fWidthField setEnabled: NO];
297 /* if the sender is the Anamorphic checkbox, record the state
298 of KeepAspect Ratio so it can be reset if Anamorphic is unchecked again */
299 if (sender == fAnamorphicPopUp)
301 keepAspectRatioPreviousState = [fRatioCheck state];
303 [fRatioCheck setState:NSOffState];
304 [fRatioCheck setEnabled: NO];
307 [fHeightStepper setEnabled: NO];
308 [fHeightField setEnabled: NO];
313 job->width = [fWidthStepper intValue];
314 job->height = [fHeightStepper intValue];
315 job->pixel_ratio = 0;
316 [fWidthStepper setEnabled: YES];
317 [fWidthField setEnabled: YES];
318 [fHeightStepper setEnabled: YES];
319 [fHeightField setEnabled: YES];
320 [fRatioCheck setEnabled: YES];
321 /* if the sender is the Anamorphic checkbox, we return the
322 keep AR checkbox to its previous state */
323 if (sender == fAnamorphicPopUp)
325 [fRatioCheck setState:keepAspectRatioPreviousState];
330 job->keep_ratio = ( [fRatioCheck state] == NSOnState );
332 fPictureFilterSettings.deinterlace = [fDeinterlacePopUp indexOfSelectedItem];
333 /* if the gui deinterlace settings are fast through slowest, the job->deinterlace
334 value needs to be set to one, for the job as well as the previews showing deinterlacing
335 otherwise set job->deinterlace to 0 or "off" */
336 if (fPictureFilterSettings.deinterlace > 0)
338 job->deinterlace = 1;
342 job->deinterlace = 0;
344 fPictureFilterSettings.denoise = [fDenoisePopUp indexOfSelectedItem];
346 fPictureFilterSettings.detelecine = [fDetelecineCheck state];
348 if ([fDeblockField stringValue] == @"Off")
350 fPictureFilterSettings.deblock = 0;
354 fPictureFilterSettings.deblock = [fDeblockField intValue];
357 fPictureFilterSettings.decomb = [fDecombPopUp indexOfSelectedItem];
359 if( job->keep_ratio )
361 if( sender == fWidthStepper || sender == fRatioCheck ||
362 sender == fCropTopStepper || sender == fCropBottomStepper )
364 hb_fix_aspect( job, HB_KEEP_WIDTH );
365 if( job->height > fTitle->height )
367 job->height = fTitle->height;
368 hb_fix_aspect( job, HB_KEEP_HEIGHT );
373 hb_fix_aspect( job, HB_KEEP_HEIGHT );
374 if( job->width > fTitle->width )
376 job->width = fTitle->width;
377 hb_fix_aspect( job, HB_KEEP_WIDTH );
380 // hb_get_preview can't handle sizes that are larger than the original title
382 if( job->width > fTitle->width )
383 job->width = fTitle->width;
385 if( job->height > fTitle->height )
386 job->height = fTitle->height;
389 [fWidthStepper setIntValue: job->width];
390 [fWidthField setIntValue: job->width];
391 if( [fAnamorphicPopUp indexOfSelectedItem] < 2 )
393 [fHeightStepper setIntValue: job->height];
394 [fHeightField setIntValue: job->height];
396 [fCropTopStepper setIntValue: job->crop[0]];
397 [fCropTopField setIntValue: job->crop[0]];
398 [fCropBottomStepper setIntValue: job->crop[1]];
399 [fCropBottomField setIntValue: job->crop[1]];
400 [fCropLeftStepper setIntValue: job->crop[2]];
401 [fCropLeftField setIntValue: job->crop[2]];
402 [fCropRightStepper setIntValue: job->crop[3]];
403 [fCropRightField setIntValue: job->crop[3]];
404 /* Sanity Check Here for < 16 px preview to avoid
405 crashing hb_get_preview. In fact, just for kicks
406 lets getting previews at a min limit of 32, since
407 no human can see any meaningful detail below that */
408 if (job->width >= 64 && job->height >= 64)
410 // Purge the existing picture previews so they get recreated the next time
412 [self purgeImageCache];
413 [self displayPreview];
417 - (IBAction) PreviousPicture: (id) sender
424 [self displayPreview];
427 - (IBAction) NextPicture: (id) sender
434 [self displayPreview];
437 - (IBAction) ClosePanel: (id) sender
439 if ([delegate respondsToSelector:@selector(pictureSettingsDidChange)])
440 [delegate pictureSettingsDidChange];
442 [NSApp endSheet:[self window]];
443 [[self window] orderOut:self];
450 - (void) setAutoCrop: (BOOL) setting
455 - (BOOL) allowLooseAnamorphic
457 return allowLooseAnamorphic;
460 - (void) setAllowLooseAnamorphic: (BOOL) setting
462 allowLooseAnamorphic = setting;
467 return fPictureFilterSettings.detelecine;
470 - (void) setDetelecine: (int) setting
472 fPictureFilterSettings.detelecine = setting;
477 return fPictureFilterSettings.deinterlace;
480 - (void) setDeinterlace: (int) setting {
481 fPictureFilterSettings.deinterlace = setting;
485 return fPictureFilterSettings.decomb;
488 - (void) setDecomb: (int) setting {
489 fPictureFilterSettings.decomb = setting;
493 return fPictureFilterSettings.denoise;
496 - (void) setDenoise: (int) setting
498 fPictureFilterSettings.denoise = setting;
503 return fPictureFilterSettings.deblock;
506 - (void) setDeblock: (int) setting
508 fPictureFilterSettings.deblock = setting;
511 - (void)showPanelInWindow: (NSWindow *)fWindow forTitle: (hb_title_t *)title
513 [self SetTitle:title];
515 [NSApp beginSheet:[self window]
516 modalForWindow:fWindow
523 // This function converts an image created by libhb (specified via pictureIndex) into
524 // an NSImage suitable for the GUI code to use. If removeBorders is YES,
525 // makeImageForPicture crops the image generated by libhb stripping off the gray
526 // border around the content. This is the low-level method that generates the image.
527 // -imageForPicture calls this function whenever it can't find an image in its cache.
528 + (NSImage *) makeImageForPicture: (int)pictureIndex
529 libhb:(hb_handle_t*)handle
530 title:(hb_title_t*)title
531 removeBorders:(BOOL)removeBorders
535 // |<---------- title->width ----------->|
536 // | |<---- title->job->width ---->| |
538 // .......................................
539 // ....+-----------------------------+....
540 // ....| |....<-- gray border
543 // ....| |<------- image
549 // ....+-----------------------------+....
550 // .......................................
552 static uint8_t * buffer;
553 static int bufferSize;
555 // Make sure we have a big enough buffer to receive the image from libhb. libhb
556 // creates images with a one-pixel border around the original content. Hence we
557 // add 2 pixels horizontally and vertically to the buffer size.
558 int srcWidth = title->width + 2;
559 int srcHeight= title->height + 2;
561 newSize = srcWidth * srcHeight * 4;
562 if( bufferSize < newSize )
564 bufferSize = newSize;
565 buffer = (uint8_t *) realloc( buffer, bufferSize );
568 hb_get_preview( handle, title, pictureIndex, buffer );
570 // Create an NSBitmapImageRep and copy the libhb image into it, converting it from
571 // libhb's format to one suitable for NSImage. Along the way, we'll strip off the
572 // border around libhb's image.
574 // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
577 int dstWidth = title->job->width;
578 int dstHeight = title->job->height;
579 NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
580 NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
581 initWithBitmapDataPlanes:nil
585 samplesPerPixel:3 // ignore alpha
588 colorSpaceName:NSCalibratedRGBColorSpace
589 bitmapFormat:bitmapFormat
590 bytesPerRow:dstWidth * 4
591 bitsPerPixel:32] autorelease];
593 int borderTop = (srcHeight - dstHeight) / 2;
594 int borderLeft = (srcWidth - dstWidth) / 2;
596 UInt32 * src = (UInt32 *)buffer;
597 UInt32 * dst = (UInt32 *)[imgrep bitmapData];
598 src += borderTop * srcWidth; // skip top rows in src to get to first row of dst
599 src += borderLeft; // skip left pixels in src to get to first pixel of dst
600 for (int r = 0; r < dstHeight; r++)
602 for (int c = 0; c < dstWidth; c++)
603 #if TARGET_RT_LITTLE_ENDIAN
604 *dst++ = Endian32_Swap(*src++);
608 src += (srcWidth - dstWidth); // skip to next row in src
611 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
612 [img addRepresentation:imgrep];
618 // Make sure we have big enough buffer
619 static uint8_t * buffer;
620 static int bufferSize;
623 newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
624 if( bufferSize < newSize )
626 bufferSize = newSize;
627 buffer = (uint8_t *) realloc( buffer, bufferSize );
630 hb_get_preview( handle, title, pictureIndex, buffer );
632 // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
633 // We'll copy that into an NSImage swapping it to ARGB in the process. Alpha is
635 int width = title->width + 2; // hblib adds a one-pixel border to the image
636 int height = title->height + 2;
637 int numPixels = width * height;
638 NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
639 NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
640 initWithBitmapDataPlanes:nil
644 samplesPerPixel:3 // ignore alpha
647 colorSpaceName:NSCalibratedRGBColorSpace
648 bitmapFormat:bitmapFormat
649 bytesPerRow:width * 4
650 bitsPerPixel:32] autorelease];
652 UInt32 * src = (UInt32 *)buffer;
653 UInt32 * dst = (UInt32 *)[imgrep bitmapData];
654 for (int i = 0; i < numPixels; i++)
655 #if TARGET_RT_LITTLE_ENDIAN
656 *dst++ = Endian32_Swap(*src++);
661 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
662 [img addRepresentation:imgrep];
668 // Returns the preview image for the specified index, retrieving it from its internal
669 // cache or by calling makeImageForPicture if it is not cached. Generally, you should
670 // use imageForPicture so that images are cached. Calling makeImageForPicture will
671 // always generate a new copy of the image.
672 - (NSImage *) imageForPicture: (int) pictureIndex
674 // The preview for the specified index may not currently exist, so this method
675 // generates it if necessary.
676 NSString * key = [NSString stringWithFormat:@"%d", pictureIndex];
677 NSImage * theImage = [fPicturePreviews objectForKey:key];
680 theImage = [PictureController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle removeBorders: NO];
681 [fPicturePreviews setObject:theImage forKey:key];
686 // Purges all images from the cache. The next call to imageForPicture will cause a new
687 // image to be generated.
688 - (void) purgeImageCache
690 [fPicturePreviews removeAllObjects];
695 @implementation PictureController (Private)
698 // -[PictureController(Private) optimalViewSizeForImageSize:]
700 // Given the size of the preview image to be shown, returns the best possible
701 // size for the view.
703 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
705 // The min size is 320x240
706 CGFloat minWidth = 320.0;
707 CGFloat minHeight = 240.0;
709 // The max size of the view is when the sheet is taking up 85% of the screen.
710 NSSize screenSize = [[NSScreen mainScreen] frame].size;
711 NSSize sheetSize = [[self window] frame].size;
712 NSSize viewAreaSize = [fPictureViewArea frame].size;
713 CGFloat paddingX = sheetSize.width - viewAreaSize.width;
714 CGFloat paddingY = sheetSize.height - viewAreaSize.height;
715 CGFloat maxWidth = (0.85 * screenSize.width) - paddingX;
716 CGFloat maxHeight = (0.85 * screenSize.height) - paddingY;
718 NSSize resultSize = imageSize;
720 // Its better to have a view that's too small than a view that's too big, so
721 // apply the maximum constraints last.
722 if( resultSize.width < minWidth )
724 resultSize.height *= (minWidth / resultSize.width);
725 resultSize.width = minWidth;
727 if( resultSize.height < minHeight )
729 resultSize.width *= (minHeight / resultSize.height);
730 resultSize.height = minHeight;
732 if( resultSize.width > maxWidth )
734 resultSize.height *= (maxWidth / resultSize.width);
735 resultSize.width = maxWidth;
737 if( resultSize.height > maxHeight )
739 resultSize.width *= (maxHeight / resultSize.height);
740 resultSize.height = maxHeight;
747 // -[PictureController(Private) resizePanelForViewSize:animate:]
749 // Resizes the entire sheet to accomodate a view of a particular size.
751 - (void)resizeSheetForViewSize: (NSSize)viewSize
753 // Figure out the deltas for the new frame area
754 NSSize currentSize = [fPictureViewArea frame].size;
755 CGFloat deltaX = viewSize.width - currentSize.width;
756 CGFloat deltaY = viewSize.height - currentSize.height;
758 // Now resize the whole panel by those same deltas, but don't exceed the min
759 NSRect frame = [[self window] frame];
760 NSSize maxSize = [[self window] maxSize];
761 NSSize minSize = [[self window] minSize];
762 frame.size.width += deltaX;
763 frame.size.height += deltaY;
764 if( frame.size.width < minSize.width )
766 frame.size.width = minSize.width;
768 if( frame.size.height < minSize.height )
770 frame.size.height = minSize.height;
773 // But now the sheet is off-center, so also shift the origin to center it and
774 // keep the top aligned.
775 if( frame.size.width != [[self window] frame].size.width )
776 frame.origin.x -= (deltaX / 2.0);
778 if( frame.size.height != [[self window] frame].size.height )
779 frame.origin.y -= deltaY;
781 [[self window] setFrame:frame display:YES animate:YES];
785 // -[PictureController(Private) setViewSize:]
787 // Changes the view's size and centers it vertically inside of its area.
788 // Assumes resizeSheetForViewSize: has already been called.
790 - (void)setViewSize: (NSSize)viewSize
792 [fPictureView setFrameSize:viewSize];
794 // center it vertically
795 NSPoint origin = [fPictureViewArea frame].origin;
796 origin.y += ([fPictureViewArea frame].size.height -
797 [fPictureView frame].size.height) / 2.0;
798 [fPictureView setFrameOrigin:origin];
802 // -[PictureController(Private) viewNeedsToResizeToSize:]
804 // Returns YES if the view will need to resize to match the given size.
806 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
808 NSSize viewSize = [fPictureView frame].size;
809 return (newSize.width != viewSize.width || newSize.height != viewSize.height);