On 2011 Jan 11, at 17:54, Dave Fernandes wrote:

> And I'd love to know a trick to bring the correct tab to the front when 
> undoing/redoing changes in a tabbed interface.

Well, the way I've seen it done in Apple Sample Code is to avoid a tabbed 
interface, do editing of objects in sheets, and the sheets have their own 
managed object contexts and own undo managers.  But I don't like that because 
the undo chain often appears to be broken from the user's viewpoint, objects 
must be copied between mocs when editing ends, and as far as multiple undo 
managers, well, I say "Thank you", but one undo manager is more than enough for 
me to handle on a good day.

So, I've done the switching of tabs as you suggest, and am fairly pleased with 
the results, except that I'm sure I spent much more time on Undo than is worth 
it to my users.

First of all, you make the simplifying assumption that the "correct" tab view 
item is the one that is being displayed at the time that the Undo Group was 
ended.  I find this to be correct about 90% of of the time.  Then, you register 
an undo action to select this tab view item in your current undo group.

It sounds simple, but when you try it, all kinds of wacky things happen.  You 
also need to consider Redo.  The plot thickens.

I do it via a notification with two observers:

[[NSNotificationCenter defaultCenter] addObserver:self
                  selector:@selector(registerTabViewForUndo:)
                      name:SSYUndoManagerWillEndUndoGroupNotification
                    object:[self undoManager]] ;
[[NSNotificationCenter defaultCenter] addObserver:self
                  selector:@selector(registerTabViewForUndo:)
                      name:NSUndoManagerCheckpointNotification
                    object:[self undoManager]] ;

SSYUndoManagerWillEndUndoGroupNotification is, as the name implies, a 
notification that I post when I end an undo grouping, usually a super-group 
that includes a half dozen or so Core Data undo groupings.  (But that's another 
topic.)

Now here's the method that it triggers.  The comments are the most interesting 
part.

- (void)registerTabViewForUndo:(NSNotification*)note {
    // As always, I use [[self managedObjectContext] hasChanges]
    // instead of [self isDocumentEdited] because the former
    // often returns YES when the latter is NO, and this
    // seems to be the true answer.
    if (![[self managedObjectContext] hasChanges]) {
        return ;
    }
    
    // Some tab view items do not have any controls which
    // affect the data model.
    if (![[self bkmslfWinCon] activeTabViewIsUndoable]) {
        return ;
    }
    
    // Determine whether or not to register a change to the current tab view in 
the current
    // undo group.  The code with follows looks weird and unsymmetrical, but it 
implements
    // (efficiently) what has been determined by careful experimentation to be 
the
    // following Magic Algorithm:
    // doRegister if any one of:
    //   1.  SSYUndoManagerWillEndUndoGroupNotification && !isUndoing
    //       (Register during normal "do" changes by the user, to be used 
during later Undo.)
    //   2.  NSUndoManagerCheckpointNotification && isUndoing
    //       (Register during undo operations, to be used during later Redo.)
    //   3.  NSUndoManagerCheckpointNotification && isRedoing
    //       (Register during redo operations, to be used during later Undo.)
    // I tried combining cases 1 and 2 above, by observing 
NSUndoManagerCheckpointNotification
    // without regard to isUndoing, but this caused a proliferation of undo 
groups, needing to
    // click Undo several times before the desired action was undone.
    // I also tried to use NSUndoManagerWillCloseUndoGroupNotification instead 
of
    // SSYUndoManagerWillEndUndoGroupNotification, but this caused a "bad group 
state -
    // attempt to close a nested group with no group open" to be logged from 
GCUndoManager
    // immediately upon doing an action.
    NSString* name = [note name] ;
    BOOL doRegister = NO ;    
    if ([name isEqualToString:NSUndoManagerCheckpointNotification]) {
        doRegister = [[self undoManager] isUndoing] || [[self undoManager] 
isRedoing] ;
    }
    else if ([name isEqualToString:SSYUndoManagerWillEndUndoGroupNotification]) 
{
        doRegister = ![[self undoManager] isUndoing] ;
    }
    
    if (doRegister) {
        // Register revealing the tab view item in the current undo group
        NSString* currentTabViewIdentifier = [[self bkmslfWinCon] 
activeTabViewItemIdentifier] ;
        [[self undoManager] registerUndoWithTarget:self
                                          
selector:@selector(revealTabViewIdentifier:)
                                            object:currentTabViewIdentifier] ;  
      
    }
}

Of course, bkmslfWinCon is the document's window controller, and the 
-activeTabViewItemIdentifier and -revealTabViewIdentifier: methods are as their 
names imply.  I use these wrappers since I actually have multiple levels of 
tabs.

_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to