From bd2b914597a5947d11048bc79fcc7f18a3c2eeb5 Mon Sep 17 00:00:00 2001 From: armm77 Date: Fri, 15 May 2026 08:39:35 -0500 Subject: [PATCH] Review: adaptive window resizing and image centering on zoom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the user changes the zoom level (via the scale popup or the new Cmd+= / Cmd+- keyboard shortcuts), the window now resizes automatically to follow the image: - Zoom out: window shrinks to fit the scaled image, minimum 200×200 px. - Zoom in: window grows with the image up to a hard cap of 70% of the screen width and 70% of the screen height. Beyond that cap zooming continues normally and scroll bars become active. - The window center is kept fixed on every resize; if the result would go off-screen it is clamped to the visible area. - The same 70% rule is applied when an image is first opened, replacing the previous ad-hoc margin calculations. When the scaled image is smaller than the scroll view's visible area the image view is expanded to fill that area so that NSImageAlignCenter keeps the image horizontally and vertically centered instead of drifting to a corner. Zoom In / Zoom Out are wired to the existing Display menu items at launch (Cmd+= and Cmd+-). The items are disabled automatically when no image window is open. --- Applications/Review/AppController.h | 3 + Applications/Review/AppController.m | 53 ++++++++++++ .../English.lproj/Review.gorm/data.classes | 4 +- Applications/Review/ImageWindow.h | 2 + Applications/Review/ImageWindow.m | 80 +++++++++++++++++-- 5 files changed, 133 insertions(+), 9 deletions(-) diff --git a/Applications/Review/AppController.h b/Applications/Review/AppController.h index f19862d87..59b256b6a 100644 --- a/Applications/Review/AppController.h +++ b/Applications/Review/AppController.h @@ -45,4 +45,7 @@ - (void)setDefaultSize:(id)sender; //Info Panel - (void)showInfoPanel:(id)sender; + +- (void)zoomIn:(id)sender; +- (void)zoomOut:(id)sender; @end diff --git a/Applications/Review/AppController.m b/Applications/Review/AppController.m index dcf824e1e..67ae0cfb3 100644 --- a/Applications/Review/AppController.m +++ b/Applications/Review/AppController.m @@ -37,6 +37,25 @@ - (void)dealloc - (void)applicationDidFinishLaunching:(NSNotification *)notif { + NSMenu *mainMenu = [NSApp mainMenu]; + NSMenuItem *displayItem = (NSMenuItem *)[mainMenu itemWithTitle:@"Display"]; + if (displayItem) { + NSMenu *displayMenu = [displayItem submenu]; + NSMenuItem *zoomInItem = (NSMenuItem *)[displayMenu itemWithTitle:@"Zoom In"]; + NSMenuItem *zoomOutItem = (NSMenuItem *)[displayMenu itemWithTitle:@"Zoom Out"]; + if (zoomInItem) { + [zoomInItem setTarget:self]; + [zoomInItem setAction:@selector(zoomIn:)]; + [zoomInItem setKeyEquivalent:@"="]; + [zoomInItem setKeyEquivalentModifierMask:NSCommandKeyMask]; + } + if (zoomOutItem) { + [zoomOutItem setTarget:self]; + [zoomOutItem setAction:@selector(zoomOut:)]; + [zoomOutItem setKeyEquivalent:@"-"]; + [zoomOutItem setKeyEquivalentModifierMask:NSCommandKeyMask]; + } + } } - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender @@ -149,6 +168,40 @@ - (void)saveImageAs:(id)sender } } +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem +{ + if ([menuItem action] == @selector(zoomIn:) || [menuItem action] == @selector(zoomOut:)) { + NSWindow *keyWindow = [NSApp keyWindow]; + for (ImageWindow *win in imageWindows) { + if (win.window == keyWindow) return YES; + } + return NO; + } + return YES; +} + +- (void)zoomIn:(id)sender +{ + NSWindow *keyWindow = [NSApp keyWindow]; + for (ImageWindow *win in imageWindows) { + if (win.window == keyWindow) { + [win zoomIn]; + return; + } + } +} + +- (void)zoomOut:(id)sender +{ + NSWindow *keyWindow = [NSApp keyWindow]; + for (ImageWindow *win in imageWindows) { + if (win.window == keyWindow) { + [win zoomOut]; + return; + } + } +} + - (void)imageWindowWillClose:(id)sender { if (sender) { diff --git a/Applications/Review/English.lproj/Review.gorm/data.classes b/Applications/Review/English.lproj/Review.gorm/data.classes index c1cbe5de6..84ef719e7 100644 --- a/Applications/Review/English.lproj/Review.gorm/data.classes +++ b/Applications/Review/English.lproj/Review.gorm/data.classes @@ -6,7 +6,9 @@ "openImage:", "showInfoPanel:", "saveImageAs:", - "showInspector:" + "showInspector:", + "zoomIn:", + "zoomOut:" ); Outlets = ( infoPanel diff --git a/Applications/Review/ImageWindow.h b/Applications/Review/ImageWindow.h index dba961d32..c15a5cd17 100644 --- a/Applications/Review/ImageWindow.h +++ b/Applications/Review/ImageWindow.h @@ -68,6 +68,8 @@ - (NSString *)imageProgressive; - (void)scaleImageFromPopup:(id)sender; +- (void)zoomIn; +- (void)zoomOut; @end diff --git a/Applications/Review/ImageWindow.m b/Applications/Review/ImageWindow.m index 93770ed58..7869c8737 100644 --- a/Applications/Review/ImageWindow.m +++ b/Applications/Review/ImageWindow.m @@ -284,11 +284,12 @@ - (id)initWithContentsOfFile:(NSString *)path NSLog(@"Representaion: %li x %li", rep.pixelsWide, rep.pixelsHigh); } frame = [NSWindow frameRectForContentRect:frame styleMask:wMask]; - if (imageSize.width > ([[NSScreen mainScreen] frame].size.width - 64)) { - frame.size.width = [[NSScreen mainScreen] frame].size.width - 164; - } - if (imageSize.height > ([[NSScreen mainScreen] frame].size.height - 64)) { - frame.size.height = [[NSScreen mainScreen] frame].size.height - 64; + { + NSRect sf = [[NSScreen mainScreen] visibleFrame]; + CGFloat maxW = floor(sf.size.width * 0.70); + CGFloat maxH = floor(sf.size.height * 0.70); + if (frame.size.width > maxW) frame.size.width = maxW; + if (frame.size.height > maxH) frame.size.height = maxH; } if (frame.size.width < 100) frame.size.width = 100; @@ -302,7 +303,13 @@ - (id)initWithContentsOfFile:(NSString *)path [_window setReleasedWhenClosed:YES]; [_window setDelegate:self]; [_window setFrame:frame display:YES]; - [_window setMaxSize:frame.size]; + { + NSRect sf = [[NSScreen mainScreen] visibleFrame]; + NSSize maxContent = NSMakeSize(floor(sf.size.width * 0.70), floor(sf.size.height * 0.70)); + NSSize maxWinSize = [NSWindow frameRectForContentRect:NSMakeRect(0, 0, maxContent.width, maxContent.height) + styleMask:wMask].size; + [_window setMaxSize:maxWinSize]; + } [_window setMinSize:NSMakeSize(200, 200)]; [_window setContentView:box]; RELEASE(box); @@ -317,6 +324,35 @@ - (id)initWithContentsOfFile:(NSString *)path return self; } +- (void)_resizeWindowForImageSize:(NSSize)scaledSize +{ + NSScreen *screen = [_window screen]; + if (!screen) screen = [NSScreen mainScreen]; + NSRect sf = [screen visibleFrame]; + + CGFloat maxW = floor(sf.size.width * 0.70); + CGFloat maxH = floor(sf.size.height * 0.70); + + NSSize contentSize; + contentSize.width = MAX(200.0, MIN(scaledSize.width, maxW)); + contentSize.height = MAX(200.0, MIN(scaledSize.height, maxH)); + + NSRect newFrame = [NSWindow frameRectForContentRect:NSMakeRect(0, 0, contentSize.width, contentSize.height) + styleMask:[_window styleMask]]; + + NSRect cur = [_window frame]; + newFrame.origin.x = NSMidX(cur) - newFrame.size.width / 2.0; + newFrame.origin.y = NSMidY(cur) - newFrame.size.height / 2.0; + + // Clamp so window stays on screen + if (newFrame.origin.x < sf.origin.x) newFrame.origin.x = sf.origin.x; + if (newFrame.origin.y < sf.origin.y) newFrame.origin.y = sf.origin.y; + if (NSMaxX(newFrame) > NSMaxX(sf)) newFrame.origin.x = NSMaxX(sf) - newFrame.size.width; + if (NSMaxY(newFrame) > NSMaxY(sf)) newFrame.origin.y = NSMaxY(sf) - newFrame.size.height; + + [_window setFrame:newFrame display:YES]; +} + - (void)scaleImageFromPopup:(id)sender { NSString *title = [scalePopup titleOfSelectedItem]; @@ -328,13 +364,41 @@ - (void)scaleImageFromPopup:(id)sender NSSize scaledSize = NSMakeSize(round(baseSize.width * factor), round(baseSize.height * factor)); - /* Resize the image to the scaled size — NSImageView will stretch it */ [_image setSize:scaledSize]; - [imageView setFrameSize:scaledSize]; + + /* Resize window first so the scroll view has its final content area size */ + [self _resizeWindowForImageSize:scaledSize]; + + /* Size imageView to at least fill the visible area so NSImageAlignCenter + keeps the image centered when it is smaller than the scroll view */ + NSScrollView *sv = [imageView enclosingScrollView]; + NSSize contentSize = sv ? [sv contentSize] : scaledSize; + NSSize viewSize = NSMakeSize(MAX(scaledSize.width, contentSize.width), + MAX(scaledSize.height, contentSize.height)); + + [imageView setFrameSize:viewSize]; [imageView setImage:nil]; /* force redraw */ [imageView setImage:_image]; } +- (void)zoomIn +{ + NSInteger idx = [scalePopup indexOfSelectedItem]; + if (idx < [scalePopup numberOfItems] - 1) { + [scalePopup selectItemAtIndex:idx + 1]; + [self scaleImageFromPopup:scalePopup]; + } +} + +- (void)zoomOut +{ + NSInteger idx = [scalePopup indexOfSelectedItem]; + if (idx > 0) { + [scalePopup selectItemAtIndex:idx - 1]; + [self scaleImageFromPopup:scalePopup]; + } +} + - (void)dealloc { RELEASE(_window);