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 #include "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 [fVFRCheck setState:fPictureFilterSettings.vfr];
117 [fDetelecineCheck setState:fPictureFilterSettings.detelecine];
118 [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
119 [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise];
120 [fDeblockCheck setState: fPictureFilterSettings.deblock];
123 MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
124 MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
125 [self SettingsChanged: nil];
128 /* we use this to setup the initial picture filters upon first launch, after that their states
129 are maintained across different sources */
130 - (void) setInitialPictureFilters
132 /* we use a popup to show the deinterlace settings */
133 [fDeinterlacePopUp removeAllItems];
134 [fDeinterlacePopUp addItemWithTitle: @"None"];
135 [fDeinterlacePopUp addItemWithTitle: @"Fast"];
136 [fDeinterlacePopUp addItemWithTitle: @"Slow"];
137 [fDeinterlacePopUp addItemWithTitle: @"Slower"];
139 /* Set deinterlaces level according to the integer in the main window */
140 [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
142 /* we use a popup to show the denoise settings */
143 [fDenoisePopUp removeAllItems];
144 [fDenoisePopUp addItemWithTitle: @"None"];
145 [fDenoisePopUp addItemWithTitle: @"Weak"];
146 [fDenoisePopUp addItemWithTitle: @"Medium"];
147 [fDenoisePopUp addItemWithTitle: @"Strong"];
148 /* Set denoises level according to the integer in the main window */
149 [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise];
151 /* we use a popup to show the decomb settings */
152 [fDecombPopUp removeAllItems];
153 [fDecombPopUp addItemWithTitle: @"None"];
154 [fDecombPopUp addItemWithTitle: @"Default"];
155 [fDecombPopUp addItemWithTitle: @"Custom"];
156 /* Set denoises level according to the integer in the main window */
157 [fDecombPopUp selectItemAtIndex: fPictureFilterSettings.decomb];
161 // Adjusts the window to draw the current picture (fPicture) adjusting its size as
162 // necessary to display as much of the picture as possible.
163 - (void) displayPreview
165 [fPictureView setImage: [self imageForPicture: fPicture]];
167 NSSize displaySize = NSMakeSize( (float)fTitle->width, (float)fTitle->height );
168 /* Set the picture size display fields below the Preview Picture*/
169 if( fTitle->job->pixel_ratio == 1 ) // Original PAR Implementation
171 output_width = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3];
172 output_height = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1];
173 display_width = output_width * fTitle->job->pixel_aspect_width / fTitle->job->pixel_aspect_height;
174 [fInfoField setStringValue:[NSString stringWithFormat:
175 @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
176 fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
177 displaySize.width *= ((float)fTitle->job->pixel_aspect_width) / ((float)fTitle->job->pixel_aspect_height);
179 else if (fTitle->job->pixel_ratio == 2) // Loose Anamorphic
181 display_width = output_width * output_par_width / output_par_height;
182 [fInfoField setStringValue:[NSString stringWithFormat:
183 @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
184 fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
186 /* FIXME: needs to be fixed so that the picture window does not resize itself on the first
187 anamorphic width drop
189 if (fTitle->width - 8 < output_width)
191 displaySize.width *= ((float)output_par_width) / ((float)output_par_height);
194 else // No Anamorphic
196 [fInfoField setStringValue: [NSString stringWithFormat:
197 @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
198 fTitle->job->width, fTitle->job->height]];
201 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
202 if( [self viewNeedsToResizeToSize:viewSize] )
204 [self resizeSheetForViewSize:viewSize];
205 [self setViewSize:viewSize];
208 // Show the scaled text (use the height to check since the width can vary
209 // with anamorphic video).
210 if( ((int)viewSize.height) != fTitle->height )
212 float scale = viewSize.width / ((float)fTitle->width);
213 NSString *scaleString = [NSString stringWithFormat:
214 NSLocalizedString( @" (Preview scaled to %.0f%% actual size)",
215 @"String shown when a preview is scaled" ),
217 [fInfoField setStringValue:
218 [[fInfoField stringValue] stringByAppendingString:scaleString]];
221 [fPrevButton setEnabled: ( fPicture > 0 )];
222 [fNextButton setEnabled: ( fPicture < 9 )];
225 - (IBAction) SettingsChanged: (id) sender
227 hb_job_t * job = fTitle->job;
229 if( [fAnamorphicPopUp indexOfSelectedItem] > 0 )
231 if ([fAnamorphicPopUp indexOfSelectedItem] == 2) // Loose anamorphic
233 job->pixel_ratio = 2;
234 [fWidthStepper setEnabled: YES];
235 [fWidthField setEnabled: YES];
236 /* We set job->width and call hb_set_anamorphic_size in libhb to do a "dry run" to get
237 * the values to be used by libhb for loose anamorphic
239 job->width = [fWidthStepper intValue];
240 hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
241 [fHeightStepper setIntValue: output_height];
242 [fHeightField setIntValue: output_height];
243 job->height = [fHeightStepper intValue];
246 else // must be "1" or strict anamorphic
248 [fWidthStepper setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
249 [fWidthField setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
251 /* This will show correct anamorphic height values, but
252 show distorted preview picture ratio */
253 [fHeightStepper setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
254 [fHeightField setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
255 job->width = [fWidthStepper intValue];
256 job->height = [fHeightStepper intValue];
258 job->pixel_ratio = 1;
259 [fWidthStepper setEnabled: NO];
260 [fWidthField setEnabled: NO];
263 /* if the sender is the Anamorphic checkbox, record the state
264 of KeepAspect Ratio so it can be reset if Anamorphic is unchecked again */
265 if (sender == fAnamorphicPopUp)
267 keepAspectRatioPreviousState = [fRatioCheck state];
269 [fRatioCheck setState:NSOffState];
270 [fRatioCheck setEnabled: NO];
273 [fHeightStepper setEnabled: NO];
274 [fHeightField setEnabled: NO];
279 job->width = [fWidthStepper intValue];
280 job->height = [fHeightStepper intValue];
281 job->pixel_ratio = 0;
282 [fWidthStepper setEnabled: YES];
283 [fWidthField setEnabled: YES];
284 [fHeightStepper setEnabled: YES];
285 [fHeightField setEnabled: YES];
286 [fRatioCheck setEnabled: YES];
287 /* if the sender is the Anamorphic checkbox, we return the
288 keep AR checkbox to its previous state */
289 if (sender == fAnamorphicPopUp)
291 [fRatioCheck setState:keepAspectRatioPreviousState];
297 job->keep_ratio = ( [fRatioCheck state] == NSOnState );
299 fPictureFilterSettings.deinterlace = [fDeinterlacePopUp indexOfSelectedItem];
300 /* if the gui deinterlace settings are fast through slowest, the job->deinterlace
301 value needs to be set to one, for the job as well as the previews showing deinterlacing
302 otherwise set job->deinterlace to 0 or "off" */
303 if (fPictureFilterSettings.deinterlace > 0)
305 job->deinterlace = 1;
309 job->deinterlace = 0;
311 fPictureFilterSettings.denoise = [fDenoisePopUp indexOfSelectedItem];
312 fPictureFilterSettings.vfr = [fVFRCheck state];
313 if (fPictureFilterSettings.vfr > 0)
315 [fDetelecineCheck setState:NSOnState];
316 [fDetelecineCheck setEnabled: NO];
320 [fDetelecineCheck setEnabled: YES];
322 fPictureFilterSettings.detelecine = [fDetelecineCheck state];
323 fPictureFilterSettings.deblock = [fDeblockCheck state];
325 fPictureFilterSettings.decomb = [fDecombPopUp indexOfSelectedItem];
327 autoCrop = ( [fCropMatrix selectedRow] == 0 );
328 [fCropTopStepper setEnabled: !autoCrop];
329 [fCropBottomStepper setEnabled: !autoCrop];
330 [fCropLeftStepper setEnabled: !autoCrop];
331 [fCropRightStepper setEnabled: !autoCrop];
335 memcpy( job->crop, fTitle->crop, 4 * sizeof( int ) );
339 job->crop[0] = [fCropTopStepper intValue];
340 job->crop[1] = [fCropBottomStepper intValue];
341 job->crop[2] = [fCropLeftStepper intValue];
342 job->crop[3] = [fCropRightStepper intValue];
345 if( job->keep_ratio )
347 if( sender == fWidthStepper || sender == fRatioCheck ||
348 sender == fCropTopStepper || sender == fCropBottomStepper )
350 hb_fix_aspect( job, HB_KEEP_WIDTH );
351 if( job->height > fTitle->height )
353 job->height = fTitle->height;
354 hb_fix_aspect( job, HB_KEEP_HEIGHT );
359 hb_fix_aspect( job, HB_KEEP_HEIGHT );
360 if( job->width > fTitle->width )
362 job->width = fTitle->width;
363 hb_fix_aspect( job, HB_KEEP_WIDTH );
368 [fWidthStepper setIntValue: job->width];
369 [fWidthField setIntValue: job->width];
370 if( [fAnamorphicPopUp indexOfSelectedItem] < 2 )
372 [fHeightStepper setIntValue: job->height];
373 [fHeightField setIntValue: job->height];
375 [fCropTopStepper setIntValue: job->crop[0]];
376 [fCropTopField setIntValue: job->crop[0]];
377 [fCropBottomStepper setIntValue: job->crop[1]];
378 [fCropBottomField setIntValue: job->crop[1]];
379 [fCropLeftStepper setIntValue: job->crop[2]];
380 [fCropLeftField setIntValue: job->crop[2]];
381 [fCropRightStepper setIntValue: job->crop[3]];
382 [fCropRightField setIntValue: job->crop[3]];
383 /* Sanity Check Here for < 16 px preview to avoid
384 crashing hb_get_preview. In fact, just for kicks
385 lets getting previews at a min limit of 32, since
386 no human can see any meaningful detail below that */
387 if (job->width >= 64 && job->height >= 64)
389 // Purge the existing picture previews so they get recreated the next time
391 [self purgeImageCache];
392 [self displayPreview];
396 - (IBAction) PreviousPicture: (id) sender
403 [self displayPreview];
406 - (IBAction) NextPicture: (id) sender
413 [self displayPreview];
416 - (IBAction) ClosePanel: (id) sender
418 if ([delegate respondsToSelector:@selector(pictureSettingsDidChange)])
419 [delegate pictureSettingsDidChange];
421 [NSApp endSheet:[self window]];
422 [[self window] orderOut:self];
429 - (void) setAutoCrop: (BOOL) setting
434 - (BOOL) allowLooseAnamorphic
436 return allowLooseAnamorphic;
439 - (void) setAllowLooseAnamorphic: (BOOL) setting
441 allowLooseAnamorphic = setting;
446 return fPictureFilterSettings.detelecine;
449 - (void) setDetelecine: (int) setting
451 fPictureFilterSettings.detelecine = setting;
456 return fPictureFilterSettings.vfr;
459 - (void) setVFR: (int) setting
461 fPictureFilterSettings.vfr = setting;
466 return fPictureFilterSettings.deinterlace;
469 - (void) setDeinterlace: (int) setting {
470 fPictureFilterSettings.deinterlace = setting;
474 return fPictureFilterSettings.decomb;
477 - (void) setDecomb: (int) setting {
478 fPictureFilterSettings.decomb = setting;
482 return fPictureFilterSettings.denoise;
485 - (void) setDenoise: (int) setting
487 fPictureFilterSettings.denoise = setting;
492 return fPictureFilterSettings.deblock;
495 - (void) setDeblock: (int) setting
497 fPictureFilterSettings.deblock = setting;
500 - (void)showPanelInWindow: (NSWindow *)fWindow forTitle: (hb_title_t *)title
502 [self SetTitle:title];
504 [NSApp beginSheet:[self window]
505 modalForWindow:fWindow
512 // This function converts an image created by libhb (specified via pictureIndex) into
513 // an NSImage suitable for the GUI code to use. If removeBorders is YES,
514 // makeImageForPicture crops the image generated by libhb stripping off the gray
515 // border around the content. This is the low-level method that generates the image.
516 // -imageForPicture calls this function whenever it can't find an image in its cache.
517 + (NSImage *) makeImageForPicture: (int)pictureIndex
518 libhb:(hb_handle_t*)handle
519 title:(hb_title_t*)title
520 removeBorders:(BOOL)removeBorders
524 // |<---------- title->width ----------->|
525 // | |<---- title->job->width ---->| |
527 // .......................................
528 // ....+-----------------------------+....
529 // ....| |....<-- gray border
532 // ....| |<------- image
538 // ....+-----------------------------+....
539 // .......................................
541 static uint8_t * buffer;
542 static int bufferSize;
544 // Make sure we have a big enough buffer to receive the image from libhb. libhb
545 // creates images with a one-pixel border around the original content. Hence we
546 // add 2 pixels horizontally and vertically to the buffer size.
547 int srcWidth = title->width + 2;
548 int srcHeight= title->height + 2;
550 newSize = srcWidth * srcHeight * 4;
551 if( bufferSize < newSize )
553 bufferSize = newSize;
554 buffer = (uint8_t *) realloc( buffer, bufferSize );
557 hb_get_preview( handle, title, pictureIndex, buffer );
559 // Create an NSBitmapImageRep and copy the libhb image into it, converting it from
560 // libhb's format to one suitable for NSImage. Along the way, we'll strip off the
561 // border around libhb's image.
563 // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
566 int dstWidth = title->job->width;
567 int dstHeight = title->job->height;
568 NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
569 NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
570 initWithBitmapDataPlanes:nil
574 samplesPerPixel:3 // ignore alpha
577 colorSpaceName:NSCalibratedRGBColorSpace
578 bitmapFormat:bitmapFormat
579 bytesPerRow:dstWidth * 4
580 bitsPerPixel:32] autorelease];
582 int borderTop = (srcHeight - dstHeight) / 2;
583 int borderLeft = (srcWidth - dstWidth) / 2;
585 UInt32 * src = (UInt32 *)buffer;
586 UInt32 * dst = (UInt32 *)[imgrep bitmapData];
587 src += borderTop * srcWidth; // skip top rows in src to get to first row of dst
588 src += borderLeft; // skip left pixels in src to get to first pixel of dst
589 for (int r = 0; r < dstHeight; r++)
591 for (int c = 0; c < dstWidth; c++)
592 #if TARGET_RT_LITTLE_ENDIAN
593 *dst++ = Endian32_Swap(*src++);
597 src += (srcWidth - dstWidth); // skip to next row in src
600 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
601 [img addRepresentation:imgrep];
607 // Make sure we have big enough buffer
608 static uint8_t * buffer;
609 static int bufferSize;
612 newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
613 if( bufferSize < newSize )
615 bufferSize = newSize;
616 buffer = (uint8_t *) realloc( buffer, bufferSize );
619 hb_get_preview( handle, title, pictureIndex, buffer );
621 // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
622 // We'll copy that into an NSImage swapping it to ARGB in the process. Alpha is
624 int width = title->width + 2; // hblib adds a one-pixel border to the image
625 int height = title->height + 2;
626 int numPixels = width * height;
627 NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
628 NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
629 initWithBitmapDataPlanes:nil
633 samplesPerPixel:3 // ignore alpha
636 colorSpaceName:NSCalibratedRGBColorSpace
637 bitmapFormat:bitmapFormat
638 bytesPerRow:width * 4
639 bitsPerPixel:32] autorelease];
641 UInt32 * src = (UInt32 *)buffer;
642 UInt32 * dst = (UInt32 *)[imgrep bitmapData];
643 for (int i = 0; i < numPixels; i++)
644 #if TARGET_RT_LITTLE_ENDIAN
645 *dst++ = Endian32_Swap(*src++);
650 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
651 [img addRepresentation:imgrep];
657 // Returns the preview image for the specified index, retrieving it from its internal
658 // cache or by calling makeImageForPicture if it is not cached. Generally, you should
659 // use imageForPicture so that images are cached. Calling makeImageForPicture will
660 // always generate a new copy of the image.
661 - (NSImage *) imageForPicture: (int) pictureIndex
663 // The preview for the specified index may not currently exist, so this method
664 // generates it if necessary.
665 NSString * key = [NSString stringWithFormat:@"%d", pictureIndex];
666 NSImage * theImage = [fPicturePreviews objectForKey:key];
669 theImage = [PictureController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle removeBorders: NO];
670 [fPicturePreviews setObject:theImage forKey:key];
675 // Purges all images from the cache. The next call to imageForPicture will cause a new
676 // image to be generated.
677 - (void) purgeImageCache
679 [fPicturePreviews removeAllObjects];
684 @implementation PictureController (Private)
687 // -[PictureController(Private) optimalViewSizeForImageSize:]
689 // Given the size of the preview image to be shown, returns the best possible
690 // size for the OpenGL view.
692 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
694 // The min size is 320x240
695 float minWidth = 320.0;
696 float minHeight = 240.0;
698 // The max size of the view is when the sheet is taking up 85% of the screen.
699 NSSize screenSize = [[NSScreen mainScreen] frame].size;
700 NSSize sheetSize = [[self window] frame].size;
701 NSSize viewAreaSize = [fPictureViewArea frame].size;
702 float paddingX = sheetSize.width - viewAreaSize.width;
703 float paddingY = sheetSize.height - viewAreaSize.height;
704 float maxWidth = (0.85 * screenSize.width) - paddingX;
705 float maxHeight = (0.85 * screenSize.height) - paddingY;
707 NSSize resultSize = imageSize;
709 // Its better to have a view that's too small than a view that's too big, so
710 // apply the maximum constraints last.
711 if( resultSize.width < minWidth )
713 resultSize.height *= (minWidth / resultSize.width);
714 resultSize.width = minWidth;
716 if( resultSize.height < minHeight )
718 resultSize.width *= (minHeight / resultSize.height);
719 resultSize.height = minHeight;
721 if( resultSize.width > maxWidth )
723 resultSize.height *= (maxWidth / resultSize.width);
724 resultSize.width = maxWidth;
726 if( resultSize.height > maxHeight )
728 resultSize.width *= (maxHeight / resultSize.height);
729 resultSize.height = maxHeight;
736 // -[PictureController(Private) resizePanelForViewSize:animate:]
738 // Resizes the entire sheet to accomodate an OpenGL view of a particular size.
740 - (void)resizeSheetForViewSize: (NSSize)viewSize
742 // Figure out the deltas for the new frame area
743 NSSize currentSize = [fPictureViewArea frame].size;
744 float deltaX = viewSize.width - currentSize.width;
745 float deltaY = viewSize.height - currentSize.height;
747 // Now resize the whole panel by those same deltas, but don't exceed the min
748 NSRect frame = [[self window] frame];
749 NSSize maxSize = [[self window] maxSize];
750 NSSize minSize = [[self window] minSize];
751 frame.size.width += deltaX;
752 frame.size.height += deltaY;
753 if( frame.size.width < minSize.width )
755 frame.size.width = minSize.width;
757 if( frame.size.height < minSize.height )
759 frame.size.height = minSize.height;
762 // But now the sheet is off-center, so also shift the origin to center it and
763 // keep the top aligned.
764 frame.origin.x -= (deltaX / 2.0);
765 frame.origin.y -= deltaY;
767 [[self window] setFrame:frame display:YES animate:YES];
771 // -[PictureController(Private) setViewSize:]
773 // Changes the OpenGL view's size and centers it vertially inside of its area.
774 // Assumes resizeSheetForViewSize: has already been called.
776 - (void)setViewSize: (NSSize)viewSize
778 [fPictureView setFrameSize:viewSize];
780 // center it vertically
781 NSPoint origin = [fPictureViewArea frame].origin;
782 origin.y += ([fPictureViewArea frame].size.height -
783 [fPictureView frame].size.height) / 2.0;
784 [fPictureView setFrameOrigin:origin];
788 // -[PictureController(Private) viewNeedsToResizeToSize:]
790 // Returns YES if the view will need to resize to match the given size.
792 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
794 NSSize viewSize = [fPictureView frame].size;
795 return (newSize.width != viewSize.width || newSize.height != viewSize.height);