Disable undo/redo in Cocoa app
Asked Answered
K

5

6

I've implemented undo/redo the standard way (NSUndoManager) but can't figure out how I disable undo/redos when my app is in a specific state.

Users draw things in my app and when what they've drawn is uploading I disable the UI and of course don't want the user to be able to undo/redo.

I use a NSView's Undo Manager so I guess one way could be to just make that view resign first responder. Is there another way?

Krause answered 25/2, 2013 at 18:37 Comment(0)
T
1

You can finalize undo and redo with

 - (void) removeAllActions;

or remove actions for a specific target with

 - (void) removeAllActionsWithTarget: (id) target;

If you simply want to disable any actions for a time, leaving the undo stack unchanged, simply disable the Undo/Redo menu items using NSMenuValidationProtocol's

 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem;
Theodoretheodoric answered 25/2, 2013 at 20:50 Comment(1)
Yep. That did it. Had to implement (IBAction)undo:(id)sender though.Krause
J
2

If the view is the first responder, you can implement the validateMenuItem: protocol to disable or enable the menu items according to your current state.

 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
     SEL action = menuItem.action;

     if (action == @selector(undo:) ||
         action == @selector(redo:)) {
          return !uploadingImage;
     }
     return YES;
 }
Jockstrap answered 25/2, 2013 at 20:51 Comment(0)
T
1

You can finalize undo and redo with

 - (void) removeAllActions;

or remove actions for a specific target with

 - (void) removeAllActionsWithTarget: (id) target;

If you simply want to disable any actions for a time, leaving the undo stack unchanged, simply disable the Undo/Redo menu items using NSMenuValidationProtocol's

 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem;
Theodoretheodoric answered 25/2, 2013 at 20:50 Comment(1)
Yep. That did it. Had to implement (IBAction)undo:(id)sender though.Krause
A
0

The best approach I can think of is making the view's -undoManager method return nil during uploads, which will remove it from the responder chain and cause undo/redo options to be disabled for that view.

(I haven't tested this, but I'm 99% sure that the menus will ask your view for the undo manager whenever it validates the menu options.)

Asclepiadaceous answered 25/2, 2013 at 20:36 Comment(0)
T
0

I had a similar situation where I wanted to conditionally disable certain undo/redo operations when the app is in a specific state (while still allowing undo/redo for other operations).

The method of implementing - (BOOL)validateMenuItem:(NSMenuItem *)item on a view doesn't work for me (I have a document-based app on 10.12). Per the docs at https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MenuList/Articles/EnablingMenuItems.html:

If there is an object in the responder chain that implements the item’s action, NSMenu then checks to see if that object implements the validateMenuItem: or validateUserInterfaceItem: method. If it does not, then the menu item is enabled. If it does, then the enabled status of the menu item is determined by the return value of the method.

The view would have to add an undo method the does the right thing as well.

When I probed the responder chain, I found that my NSWindow was the object that responded to undo: (though it's not part of the documented interface), so my current plan is to use a custom NSWindow subclass with the imeplementation of validateMenuItem, along the lines of:

#import "Window.h"

@implementation SBXWindow

- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag screen:(NSScreen *)screen
{
    self = [super initWithContentRect:contentRect styleMask:style backing:bufferingType defer:flag screen:screen];

    return self;
}


- (BOOL)validateMenuItem:(NSMenuItem *)item
{
    // Call super imeplementation as it appears to update the menu item title (and potentially other stuff)
    BOOL result = [super validateMenuItem:item];
    if (result == NO) {
        return NO;
    }

    if (item.action == @selector(undo:) || item.action == @selector(redo:)) {
        // Add custom logic here
    }

    return result;
}

@end

However there are warnings that the undo: redo: methods aren't implemented. These can be eliminated by creating a category on NSWindow, such as:

@interface NSWindow (SBXUndoable)

- (void)undo:(id)sender;
- (void)redo:(id)sender;

@end

Not sure if there are any issues with doing that (I didn't notice any), but it does eliminate the warnings. I've since changed the class to a Swift class, which didn't have any warnings to deal with.

Thievish answered 24/12, 2016 at 17:56 Comment(0)
M
-1

The documentation is your friend. The disableUndoRegistration method of NSUndoManager has "disable" in its name. It's up to your app's controllers to decide when it's appropriate to disable and re-enable undo registration.

Moult answered 25/2, 2013 at 20:55 Comment(1)
That's about the setting of undo events, not the disabling of the undo/redo menus.Nightcap

© 2022 - 2024 — McMap. All rights reserved.