Search code examples
cocoanstableviewnstextviewnstextfieldcellnsresponder

Intercepting undo while editing a cell-based table view


I'd like to intercept or disable Cmd-Z/Shift-Cmd-Z during a text editing session for a table view cell.

It's a cell-based table view with a series of columns whose values affect each other, such that filling in some columns will populate others automatically. When the user presses Cmd-Z in the middle of an editing session, the undo manager can change the property currently being edited, with confusing results for the user.

Here's an example:

Step 1: User types ".030" in second column:

enter image description here

Step 2: User presses tab, model automatically updates columns three, four, and five:

enter image description here

Step 3: User presses Ctrl-Z, model undoes changes to columns two, three, four, and five, but editing session is still in progress, so old value shows in column three:

enter image description here

Step 4: Without typing anything, user presses tab which cancels editing, and value from column three disappears:

enter image description here

Nothing actually "wrong" is happening here, but it's confusing.

When one of the cells is being edited, I just want to intercept Cmd-Z and Shift-Cmd-Z and ignore them. I think I should be overriding -keyDown: in whatever is first responder during that editing. But what is that? The table view doesn't get those key events at all, and the cell isn't a responder either.


Solution

  • Found a working solution. In my NSTableView subclass:

    // Disable undo and redo while table's field editors have first responder status
    -(BOOL)validateMenuItem:(NSMenuItem *)menuItem {
        if (self != self.window.firstResponder)
            if (@selector(undo:) == menuItem.action || @selector(redo:) == menuItem.action)
                return NO;
        return YES;
    }
    
    // Intercept undo events while table's field editors have first responder status
    -(IBAction)undo:(id)sender {
        if (self != self.window.firstResponder)
            [self noResponderFor:_cmd];
        else
            [self.nextResponder tryToPerform:@selector(undo:) with:sender];
    }