Pseudoko Problems and Challenges

Fixing an Intermittent Crash on Quit

I had a repeatable crash on quit when quitting with a window with unsaved changes that I'd chosen not to save.

// Note 1:
// I could get a consistent crash by creating a new document, typing a "1" in
// the first cell, clicking on another cell, typing a "1", then a Close, Don't Save.
// The crash was a BAD ACCESS in -[NSMatrix setNeedsDisplayInRect:], after the
// window was hidden and dealloc was called and was 100%
// repeatable. Adding the removeFromSuperview line below fixed it.
- (void)dealloc {
  NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  [nc removeObserver:self];
  for(int  i = 0; i < 9*9; ++i) {
    PuzzleProxy *proxy = [puzzleProxys objectAtIndex:i];
    [proxy removeObserver:self forKeyPath:@"value"];
  }
  [puzzleProxys release];
  [puzzleMatrix removeFromSuperview]; // see note 1 above
  [super dealloc];
}

I still don't fully understand why this fixed the problem.

Getting Undo To Group Right

You may have noticed in Interface Builder, in its representation of your menu bar, you can click on a menu item to rename it, then click on a second item, and it has lost the first. That is because it isn't calling -[NSWindow endEditing:] to commit the edit before responding to your click, so you're editing gets discarded. That is a "data loss" bug by my reckoning. An important bug to fix.

So I threw in a few calls to -[NSWindow endEditing:] at the start of some of my menu commands and had a new problem: NSUndoManager makes an implicit undo group around an entire event, so the endEditing and the next command were being grouped as a single undoable operation: undo it, and your typing is undone also. Once again, a data loss bug.

Example: I'd written a Sudoku solver that used an NSMatrix of NSTextFields to represent the puzzle. I had an undoable "Lock" menu command to lock the current state of the puzzle to represent the printed puzzles you see in the newspaper, so those cells couldn't be changed. The user would type a "3", then "Lock" the puzzle, but because the he'd never tabbed out of the NSTextfield, "Undo Lock" would also erase the "3".

The solution I came up with isn't very elegant, but it seems to work, but led to another problem: if you typed an illegal value in the cell, you'd get a sheet on the window with an error message, because of the endEditing, but the menu command would try to operate anyway. Once I fixed that problem, the menu command "Lock" looks like this:

- (void)lock:(id)sender {
  if ([self endEditing]) { //user types '3' he means the three to be locked.
    for(int  i = 0; i < 9*9; ++i) {
      PuzzleProxy *proxy = [puzzleProxys objectAtIndex:i];
      if (0 != [proxy value]){
        [proxy setIsEditable:NO];	// <-- registers with undo manager
      }
    }
    [[self undoManager] setActionName:NSLocalizedString(@"Lock", @"")];
  }
}

where -[endEditing] looks like this:

// Finalize any in-progress editing, and if that created an implicit undogroup, close the
// group so the next command, if any, will be in its own group.
// But, if finalizing put up an error sheet, then return NO, so caller will know to 
// abort the command.
- (BOOL)endEditing {
  [[puzzleMatrix window] endEditingFor:nil];
  BOOL canContinue = (nil == [[puzzleMatrix window] attachedSheet]);
  if (canContinue) {
    NSUndoManager *undo = [self undoManager];
    if ( ! ([undo isUndoing] || [undo isRedoing]) && 1 == [undo groupingLevel]) {
      [undo endUndoGrouping];
      [undo beginUndoGrouping];
    }
  }
  return canContinue;
}

NSUndoManager makes an implicit undo group around an entire event, such as a keyDown, or menuItem click. It waits until you register the first undoable action of the event, then lays down a beginUndoGrouping before it does the registration. At the end of the event, it previously executed beginUndoGrouping, it executes a endUndoGrouping.

I don't like my solution: it feels fragile:

(1) I never find out directly from the key value coding system that validation failed, and putting up an entire error sheet is very heavy weight for the particular problem, of typing a not-a-digit into the puzzle.

(2) I have to remember to bracket all my undoable menu commands with

if ([self endEditing]) { }


Pseudoko main page.

Page last modified January 27, 2007