Revision: 4353
          http://skim-app.svn.sourceforge.net/skim-app/?rev=4353&view=rev
Author:   hofman
Date:     2008-08-05 21:25:18 +0000 (Tue, 05 Aug 2008)

Log Message:
-----------
Implement undo for bookmarks using KVO.

Modified Paths:
--------------
    trunk/SKBookmark.h
    trunk/SKBookmark.m
    trunk/SKBookmarkController.h
    trunk/SKBookmarkController.m

Modified: trunk/SKBookmark.h
===================================================================
--- trunk/SKBookmark.h  2008-08-04 21:19:17 UTC (rev 4352)
+++ trunk/SKBookmark.h  2008-08-05 21:25:18 UTC (rev 4353)
@@ -38,9 +38,6 @@
 
 #import <Cocoa/Cocoa.h>
 
-extern NSString *SKBookmarkChangedNotification;
-extern NSString *SKBookmarkWillBeRemovedNotification;
-
 enum {
     SKBookmarkTypeBookmark,
     SKBookmarkTypeFolder,
@@ -49,7 +46,6 @@
 
 @interface SKBookmark : NSObject <NSCopying> {
     SKBookmark *parent;
-    NSUndoManager *undoManager;
 }
 
 - (id)initWithPath:(NSString *)aPath pageIndex:(unsigned)aPageIndex 
label:(NSString *)aLabel;
@@ -72,16 +68,14 @@
 - (NSNumber *)pageNumber;
 
 - (NSArray *)children;
-- (void)insertChild:(SKBookmark *)child atIndex:(unsigned int)index;
-- (void)addChild:(SKBookmark *)child;
-- (void)removeChild:(SKBookmark *)child;
+- (unsigned int)countOfChildren;
+- (SKBookmark *)objectInChildrenAtIndex:(unsigned int)anIndex;
+- (void)insertObject:(SKBookmark *)child inChildrenAtIndex:(unsigned 
int)anIndex;
+- (void)removeObjectFromChildrenAtIndex:(unsigned int)anIndex;
 
 - (SKBookmark *)parent;
 - (void)setParent:(SKBookmark *)newParent;
 
-- (NSUndoManager *)undoManager;
-- (void)setUndoManager:(NSUndoManager *)newUndoManager;
-
 - (BOOL)isDescendantOf:(SKBookmark *)bookmark;
 - (BOOL)isDescendantOfArray:(NSArray *)bookmarks;
 

Modified: trunk/SKBookmark.m
===================================================================
--- trunk/SKBookmark.m  2008-08-04 21:19:17 UTC (rev 4352)
+++ trunk/SKBookmark.m  2008-08-05 21:25:18 UTC (rev 4353)
@@ -41,9 +41,6 @@
 #import "NSImage_SKExtensions.h"
 #import "NSObject_SKExtensions.h"
 
-NSString *SKBookmarkChangedNotification = @"SKBookmarkChangedNotification";
-NSString *SKBookmarkWillBeRemovedNotification = 
@"SKBookmarkWillBeRemovedNotification";
-
 static NSString *SKBookmarkTypeBookmarkString = @"bookmark";
 static NSString *SKBookmarkTypeFolderString = @"folder";
 static NSString *SKBookmarkTypeSeparatorString = @"separator";
@@ -143,7 +140,6 @@
 }
 
 - (void)dealloc {
-    [undoManager release];
     if (self != defaultPlaceholderBookmark)
         [super dealloc];
 }
@@ -162,9 +158,10 @@
 - (NSNumber *)pageNumber { return nil; }
 
 - (NSArray *)children { return nil; }
-- (void)insertChild:(SKBookmark *)child atIndex:(unsigned int)anIndex {}
-- (void)addChild:(SKBookmark *)child {}
-- (void)removeChild:(SKBookmark *)child {}
+- (unsigned int)countOfChildren { return 0; }
+- (SKBookmark *)objectInChildrenAtIndex:(unsigned int)anIndex { return nil; }
+- (void)insertObject:(SKBookmark *)child inChildrenAtIndex:(unsigned 
int)anIndex {}
+- (void)removeObjectFromChildrenAtIndex:(unsigned int)anIndex {}
 
 - (SKBookmark *)parent {
     return parent;
@@ -195,17 +192,6 @@
     return NO;
 }
 
-- (NSUndoManager *)undoManager {
-    return undoManager ? undoManager : [parent undoManager];
-}
-
-- (void)setUndoManager:(NSUndoManager *)newUndoManager {
-    if (undoManager != newUndoManager) {
-        [undoManager release];
-        undoManager = [newUndoManager retain];
-    }
-}
-
 @end
 
 #pragma mark -
@@ -232,7 +218,6 @@
 }
 
 - (void)dealloc {
-    [[self undoManager] removeAllActionsWithTarget:self];
     [alias release];
     [aliasData release];
     [label release];
@@ -307,10 +292,8 @@
 
 - (void)setLabel:(NSString *)newLabel {
     if (label != newLabel) {
-        [(SKBookmark *)[[self undoManager] prepareWithInvocationTarget:self] 
setLabel:label];
         [label release];
         label = [newLabel retain];
-        [[NSNotificationCenter defaultCenter] 
postNotificationName:SKBookmarkChangedNotification object:self];
     }
 }
 
@@ -334,7 +317,6 @@
 }
 
 - (void)dealloc {
-    [[self undoManager] removeAllActionsWithTarget:self];
     [label release];
     [children release];
     [super dealloc];
@@ -362,36 +344,33 @@
 
 - (void)setLabel:(NSString *)newLabel {
     if (label != newLabel) {
-        [(SKBookmark *)[[self undoManager] prepareWithInvocationTarget:self] 
setLabel:label];
         [label release];
         label = [newLabel retain];
-        [[NSNotificationCenter defaultCenter] 
postNotificationName:SKBookmarkChangedNotification object:self];
     }
 }
 
 - (NSArray *)children {
-    return children;
+    return [[children copy] autorelease];
 }
 
-- (void)insertChild:(SKBookmark *)child atIndex:(unsigned int)anIndex {
-    [(SKBookmark *)[[self undoManager] prepareWithInvocationTarget:self] 
removeChild:child];
-    [children insertObject:child atIndex:anIndex];
-    [child setParent:self];
-    [[NSNotificationCenter defaultCenter] 
postNotificationName:SKBookmarkChangedNotification object:self];
+- (unsigned int)countOfChildren {
+    return [children count];
 }
 
-- (void)addChild:(SKBookmark *)child {
-    [self insertChild:child atIndex:[children count]];
+- (SKBookmark *)objectInChildrenAtIndex:(unsigned int)anIndex {
+    return [children objectAtIndex:anIndex];
 }
 
-- (void)removeChild:(SKBookmark *)child {
-    [(SKBookmark *)[[self undoManager] prepareWithInvocationTarget:self] 
insertChild:child atIndex:[[self children] indexOfObject:child]];
-    [[NSNotificationCenter defaultCenter] 
postNotificationName:SKBookmarkWillBeRemovedNotification object:self];
-    [child setParent:nil];
-    [children removeObject:child];
-    [[NSNotificationCenter defaultCenter] 
postNotificationName:SKBookmarkChangedNotification object:self];
+- (void)insertObject:(SKBookmark *)child inChildrenAtIndex:(unsigned 
int)anIndex {
+    [children insertObject:child atIndex:anIndex];
+    [child setParent:self];
 }
 
+- (void)removeObjectFromChildrenAtIndex:(unsigned int)anIndex {
+    [[children objectAtIndex:anIndex] setParent:nil];
+    [children removeObjectAtIndex:anIndex];
+}
+
 @end
 
 #pragma mark -

Modified: trunk/SKBookmarkController.h
===================================================================
--- trunk/SKBookmarkController.h        2008-08-04 21:19:17 UTC (rev 4352)
+++ trunk/SKBookmarkController.h        2008-08-05 21:25:18 UTC (rev 4353)
@@ -55,10 +55,6 @@
 - (SKBookmark *)bookmarkRoot;
 
 - (void)addBookmarkForPath:(NSString *)path pageIndex:(unsigned)pageIndex 
label:(NSString *)label toFolder:(SKBookmark *)folder;
-- (void)handleApplicationWillTerminateNotification:(NSNotification 
*)notification;
-- (void)handleBookmarkChangedNotification:(NSNotification *)notification;
-- (void)handleBookmarkWillBeRemovedNotification:(NSNotification *)notification;
-- (NSString *)bookmarksFilePath;
 - (void)openBookmarks:(NSArray *)items;
 
 - (IBAction)doubleClickBookmark:(id)sender;
@@ -74,6 +70,4 @@
 
 - (NSUndoManager *)undoManager;
 
-- (void)setupToolbar;
-
 @end

Modified: trunk/SKBookmarkController.m
===================================================================
--- trunk/SKBookmarkController.m        2008-08-04 21:19:17 UTC (rev 4352)
+++ trunk/SKBookmarkController.m        2008-08-05 21:25:18 UTC (rev 4353)
@@ -71,7 +71,21 @@
 static NSString *SKRecentDocumentAliasDataKey = @"_BDAlias";
 static NSString *SKRecentDocumentSnapshotsKey = @"snapshots";
 
+static NSString *SKBookmarkChildrenKey = @"children";
+static NSString *SKBookmarkLabelKey = @"label";
 
+static NSString *SKBookmarkPropertiesObservationContext = 
@"SKBookmarkPropertiesObservationContext";
+
+
[EMAIL PROTECTED] SKBookmarkController (SKPrivate)
+- (void)setupToolbar;
+- (NSString *)bookmarksFilePath;
+- (void)handleApplicationWillTerminateNotification:(NSNotification 
*)notification;
+- (void)endEditing;
+- (void)startObservingBookmarks:(NSArray *)newBookmarks;
+- (void)stopObservingBookmarks:(NSArray *)oldBookmarks;
[EMAIL PROTECTED]
+
 @implementation SKBookmarkController
 
 static unsigned int maxRecentDocumentsCount = 0;
@@ -127,25 +141,18 @@
         }
         
         bookmarkRoot = [[SKBookmark alloc] initFolderWithChildren:bookmarks 
label:nil];
-        [bookmarkRoot setUndoManager:[self undoManager]];
+        [self startObservingBookmarks:[NSArray arrayWithObject:bookmarkRoot]];
         
                [[NSNotificationCenter defaultCenter] addObserver:self
                                                  
selector:@selector(handleApplicationWillTerminateNotification:)
                                                      
name:NSApplicationWillTerminateNotification
                                                    object:NSApp];
-               [[NSNotificationCenter defaultCenter] addObserver:self
-                                                 
selector:@selector(handleBookmarkChangedNotification:)
-                                                     
name:SKBookmarkChangedNotification
-                                                   object:nil];
-               [[NSNotificationCenter defaultCenter] addObserver:self
-                                                 
selector:@selector(handleBookmarkWillBeRemovedNotification:)
-                                                     
name:SKBookmarkWillBeRemovedNotification
-                                                   object:nil];
     }
     return sharedBookmarkController;
 }
 
 - (void)dealloc {
+    [self stopObservingBookmarks:[NSArray arrayWithObject:bookmarkRoot]];
     [[NSNotificationCenter defaultCenter] removeObserver:self];
     [bookmarkRoot release];
     [recentDocuments release];
@@ -191,7 +198,7 @@
         if ([bookmark bookmarkType] == SKBookmarkTypeBookmark) {
             message = [bookmark path];
         } else if ([bookmark bookmarkType] == SKBookmarkTypeFolder) {
-            int count = [[bookmark children] count];
+            int count = [bookmark countOfChildren];
             message = count == 1 ? NSLocalizedString(@"1 item", @"Bookmark 
folder description") : [NSString stringWithFormat:NSLocalizedString(@"%i 
items", @"Bookmark folder description"), count];
         }
     }
@@ -220,9 +227,10 @@
 }
 
 - (void)addBookmarkForPath:(NSString *)path pageIndex:(unsigned)pageIndex 
label:(NSString *)label toFolder:(SKBookmark *)folder {
+    if (folder == nil) folder = bookmarkRoot;
     SKBookmark *bookmark = [[SKBookmark alloc] initWithPath:path 
pageIndex:pageIndex label:label];
     if (bookmark) {
-        [(folder ? folder : bookmarkRoot) addChild:bookmark];
+        [folder insertObject:bookmark inChildrenAtIndex:[folder 
countOfChildren]];
         [bookmark release];
     }
 }
@@ -354,19 +362,19 @@
     SKBookmark *folder = [[[SKBookmark alloc] 
initFolderWithLabel:NSLocalizedString(@"Folder", @"default folder name")] 
autorelease];
     int rowIndex = [[outlineView selectedRowIndexes] lastIndex];
     SKBookmark *item = bookmarkRoot;
-    unsigned int idx = [[bookmarkRoot children] count];
+    unsigned int idx = [bookmarkRoot countOfChildren];
     
     if (rowIndex != NSNotFound) {
         SKBookmark *selectedItem = [outlineView itemAtRow:rowIndex];
         if ([outlineView isItemExpanded:selectedItem]) {
             item = selectedItem;
-            idx = [[item children] count];
+            idx = [item countOfChildren];
         } else {
             item = [selectedItem parent];
             idx = [[item children] indexOfObject:selectedItem] + 1;
         }
     }
-    [item insertChild:folder atIndex:idx];
+    [item insertObject:folder inChildrenAtIndex:idx];
     
     int row = [outlineView rowForItem:folder];
     [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] 
byExtendingSelection:NO];
@@ -377,19 +385,19 @@
     SKBookmark *separator = [[[SKBookmark alloc] initSeparator] autorelease];
     int rowIndex = [[outlineView selectedRowIndexes] lastIndex];
     SKBookmark *item = bookmarkRoot;
-    unsigned int idx = [[bookmarkRoot children] count];
+    unsigned int idx = [bookmarkRoot countOfChildren];
     
     if (rowIndex != NSNotFound) {
         SKBookmark *selectedItem = [outlineView itemAtRow:rowIndex];
         if ([outlineView isItemExpanded:selectedItem]) {
             item = selectedItem;
-            idx = [[item children] count];
+            idx = [item countOfChildren];
         } else {
             item = [selectedItem parent];
             idx = [[item children] indexOfObject:selectedItem] + 1;
         }
     }
-    [item insertChild:separator atIndex:idx];
+    [item insertObject:separator inChildrenAtIndex:idx];
     
     int row = [outlineView rowForItem:separator];
     [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] 
byExtendingSelection:NO];
@@ -416,6 +424,96 @@
     return [self undoManager];
 }
 
+- (void)startObservingBookmarks:(NSArray *)newBookmarks {
+    NSEnumerator *bmEnum = [newBookmarks objectEnumerator];
+    SKBookmark *bm;
+    while (bm = [bmEnum nextObject]) {
+        if ([bm bookmarkType] != SKBookmarkTypeSeparator) {
+            [bm addObserver:self forKeyPath:SKBookmarkLabelKey 
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) 
context:SKBookmarkPropertiesObservationContext];
+            if ([bm bookmarkType] == SKBookmarkTypeFolder) {
+                [bm addObserver:self forKeyPath:SKBookmarkChildrenKey 
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) 
context:SKBookmarkPropertiesObservationContext];
+                [self startObservingBookmarks:[bm children]];
+            }
+        }
+    }
+}
+
+- (void)stopObservingBookmarks:(NSArray *)oldBookmarks {
+    NSEnumerator *bmEnum = [oldBookmarks objectEnumerator];
+    SKBookmark *bm;
+    while (bm = [bmEnum nextObject]) {
+        if ([bm bookmarkType] != SKBookmarkTypeSeparator) {
+            [bm removeObserver:self forKeyPath:SKBookmarkLabelKey];
+            if ([bm bookmarkType] == SKBookmarkTypeFolder) {
+                [bm removeObserver:self forKeyPath:SKBookmarkChildrenKey];
+                [self stopObservingBookmarks:[bm children]];
+            }
+        }
+    }
+}
+
+- (void)insertObjects:(NSArray *)newChildren inChildrenOfBookmark:(SKBookmark 
*)bookmark atIndexes:(NSIndexSet *)indexes {
+    [[bookmark mutableArrayValueForKey:SKBookmarkChildrenKey] 
insertObjects:newChildren atIndexes:indexes];
+}
+
+- (void)removeObjectsFromChildrenOfBookmark:(SKBookmark *)bookmark 
atIndexes:(NSIndexSet *)indexes {
+    [[bookmark mutableArrayValueForKey:SKBookmarkChildrenKey] 
removeObjectsAtIndexes:indexes];
+}
+
+#pragma mark KVO
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object 
change:(NSDictionary *)change context:(void *)context {
+    if (context == SKBookmarkPropertiesObservationContext) {
+        SKBookmark *bookmark = (SKBookmark *)object;
+        id newValue = [change objectForKey:NSKeyValueChangeNewKey];
+        id oldValue = [change objectForKey:NSKeyValueChangeOldKey];
+        NSIndexSet *indexes = [change objectForKey:NSKeyValueChangeIndexesKey];
+        
+        if ([newValue isEqual:[NSNull null]]) newValue = nil;
+        if ([oldValue isEqual:[NSNull null]]) oldValue = nil;
+        
+        switch ([[change objectForKey:NSKeyValueChangeKindKey] 
unsignedIntValue]) {
+            case NSKeyValueChangeSetting:
+                if ([keyPath isEqualToString:SKBookmarkChildrenKey]) {
+                    NSMutableArray *old = [NSMutableArray 
arrayWithArray:oldValue];
+                    NSMutableArray *new = [NSMutableArray 
arrayWithArray:newValue];
+                    [old removeObjectsInArray:newValue];
+                    [new removeObjectsInArray:oldValue];
+                    [self stopObservingBookmarks:old];
+                    [self startObservingBookmarks:new];
+                    [[[self undoManager] prepareWithInvocationTarget:bookmark] 
setChildren:oldValue];
+                } else if ([keyPath isEqualToString:SKBookmarkLabelKey]) {
+                    [[[self undoManager] prepareWithInvocationTarget:bookmark] 
setLabel:oldValue];
+                }
+                break;
+            case NSKeyValueChangeInsertion:
+                if ([keyPath isEqualToString:SKBookmarkChildrenKey]) {
+                    [self startObservingBookmarks:newValue];
+                    [[[self undoManager] prepareWithInvocationTarget:self] 
removeObjectsFromChildrenOfBookmark:bookmark atIndexes:indexes];
+                }
+                break;
+            case NSKeyValueChangeRemoval:
+                if ([keyPath isEqualToString:SKBookmarkChildrenKey]) {
+                    [self stopObservingBookmarks:oldValue];
+                    [[[self undoManager] prepareWithInvocationTarget:self] 
insertObjects:oldValue inChildrenOfBookmark:bookmark atIndexes:indexes];
+                }
+                break;
+            case NSKeyValueChangeReplacement:
+                if ([keyPath isEqualToString:SKBookmarkChildrenKey]) {
+                    [self stopObservingBookmarks:oldValue];
+                    [self startObservingBookmarks:newValue];
+                    [[[self undoManager] prepareWithInvocationTarget:self] 
removeObjectsFromChildrenOfBookmark:bookmark atIndexes:indexes];
+                    [[[self undoManager] prepareWithInvocationTarget:self] 
insertObjects:oldValue inChildrenOfBookmark:bookmark atIndexes:indexes];
+                }
+                break;
+        }
+        
+        [outlineView reloadData];
+    } else {
+        [super observeValueForKeyPath:keyPath ofObject:object change:change 
context:context];
+    }
+}
+
 #pragma mark Notification handlers
 
 - (void)handleApplicationWillTerminateNotification:(NSNotification 
*)notification  {
@@ -433,20 +531,16 @@
     }
 }
 
-- (void)handleBookmarkWillBeRemovedNotification:(NSNotification *)notification 
 {
+- (void)endEditing {
     if ([outlineView editedRow] && [[self window] 
makeFirstResponder:outlineView] == NO)
         [[self window] endEditingFor:nil];
 }
 
-- (void)handleBookmarkChangedNotification:(NSNotification *)notification {
-    [outlineView reloadData];
-}
-
 #pragma mark NSOutlineView datasource methods
 
 - (int)outlineView:(NSOutlineView *)ov numberOfChildrenOfItem:(id)item {
     if (item == nil) item = bookmarkRoot;
-    return [[item children] count];
+    return [item countOfChildren];
 }
 
 - (BOOL)outlineView:(NSOutlineView *)ov isItemExpandable:(id)item {
@@ -455,7 +549,7 @@
 
 - (id)outlineView:(NSOutlineView *)ov child:(int)anIndex ofItem:(id)item {
     if (item == nil) item = bookmarkRoot;
-    return [[item children]  objectAtIndex:anIndex];
+    return [item  objectInChildrenAtIndex:anIndex];
 }
 
 - (id)outlineView:(NSOutlineView *)ov objectValueForTableColumn:(NSTableColumn 
*)tableColumn byItem:(id)item {
@@ -464,7 +558,7 @@
         return [NSDictionary dictionaryWithObjectsAndKeys:[item label], 
SKTextWithIconCellStringKey, [item icon], SKTextWithIconCellImageKey, nil];
     } else if ([tcID isEqualToString:@"file"]) {
         if ([item bookmarkType] == SKBookmarkTypeFolder) {
-            int count = [[item children] count];
+            int count = [item countOfChildren];
             return count == 1 ? NSLocalizedString(@"1 item", @"Bookmark folder 
description") : [NSString stringWithFormat:NSLocalizedString(@"%i items", 
@"Bookmark folder description"), count];
         } else {
             return [item path];
@@ -503,7 +597,7 @@
             } else if (item) {
                 [ov setDropItem:(SKBookmark *)[item parent] == bookmarkRoot ? 
nil : [item parent] dropChildIndex:[[[item parent] children] 
indexOfObject:item] + 1];
             } else {
-                [ov setDropItem:nil dropChildIndex:[[bookmarkRoot children] 
count]];
+                [ov setDropItem:nil dropChildIndex:[bookmarkRoot 
countOfChildren]];
             }
         }
         return [item isDescendantOfArray:[self draggedBookmarks]] ? 
NSDragOperationNone : NSDragOperationMove;
@@ -521,16 +615,18 @@
         
         if (item == nil) item = bookmarkRoot;
         
+        [self endEditing];
                while (bookmark = [bmEnum nextObject]) {
-            int bookmarkIndex = [[[bookmark parent] children] 
indexOfObject:bookmark];
-            if (item == [bookmark parent]) {
+            SKBookmark *parent = [bookmark parent];
+            int bookmarkIndex = [[parent children] indexOfObject:bookmark];
+            if (item == parent) {
                 if (anIndex > bookmarkIndex)
                     anIndex--;
                 if (anIndex == bookmarkIndex)
                     continue;
             }
-            [[bookmark parent] removeChild:bookmark];
-            [(SKBookmark *)item insertChild:bookmark atIndex:anIndex++];
+            [parent removeObjectFromChildrenAtIndex:bookmarkIndex];
+            [(SKBookmark *)item insertObject:bookmark 
inChildrenAtIndex:anIndex++];
                }
         return YES;
     }
@@ -576,8 +672,13 @@
 - (void)outlineView:(NSOutlineView *)ov deleteItems:(NSArray *)items {
     NSEnumerator *itemEnum = [[self minimumCoverForBookmarks:items] 
reverseObjectEnumerator];
     SKBookmark *item;
-    while (item = [itemEnum  nextObject])
-        [[item parent] removeChild:item];
+    [self endEditing];
+    while (item = [itemEnum  nextObject]) {
+        SKBookmark *parent = [item parent];
+        unsigned int itemIndex = [[parent children] indexOfObject:item];
+        if (itemIndex != NSNotFound)
+            [parent removeObjectFromChildrenAtIndex:itemIndex];
+    }
 }
 
 - (BOOL)outlineView:(NSOutlineView *)ov canDeleteItems:(NSArray *)items {


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Skim-app-commit mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/skim-app-commit

Reply via email to