From 7f428391f2c720a82219c05dc14642662e0ed067 Mon Sep 17 00:00:00 2001 From: "smfr%smfr.org" Date: Wed, 13 Apr 2005 05:35:27 +0000 Subject: [PATCH] Landing changes by Bruce Davidson to get Cut/Copy/Paste to work for bookmarks (bug 155484), and to fix the pasteboard string identifiers in the code (bug 287118). I made three changes to the patch: Change CHBrowserView to no longer accept bookmark types (kCaminoBookmarkListPBoardType, kWebURLsWithTitlesPboardType), because gecko can't handle them. Instead, a parent view will now handle the drop (and correctly load tabs etc). NSPastboard+Utils -containsURLData changed to test for a scheme on the NSURL. Otherwise, any random text would be accepted as url data. BookmarkViewController: -pasteBookmarks:intoFolder:index:copying fixed to keep an array of newly created bookmark items when copying, so we can correctly reveal those new items. --- .../bookmarks/AddBookmarkDialogController.mm | 4 +- camino/src/bookmarks/BookmarkButton.mm | 11 +- camino/src/bookmarks/BookmarkFolder.h | 3 +- camino/src/bookmarks/BookmarkFolder.mm | 6 +- camino/src/bookmarks/BookmarkOutlineView.h | 5 + camino/src/bookmarks/BookmarkOutlineView.mm | 34 +- camino/src/bookmarks/BookmarkToolbar.mm | 46 +-- camino/src/bookmarks/BookmarkViewController.h | 15 +- .../src/bookmarks/BookmarkViewController.mm | 340 ++++++++++++------ camino/src/browser/BrowserTabBarView.mm | 9 +- camino/src/browser/BrowserTabView.mm | 39 +- camino/src/browser/BrowserTabViewItem.mm | 3 +- camino/src/embedding/CHBrowserView.mm | 4 +- camino/src/extensions/ExtendedOutlineView.h | 7 + camino/src/extensions/ExtendedOutlineView.mm | 44 +++ camino/src/extensions/NSPasteboard+Utils.h | 11 +- camino/src/extensions/NSPasteboard+Utils.mm | 135 ++++++- 17 files changed, 516 insertions(+), 200 deletions(-) diff --git a/camino/src/bookmarks/AddBookmarkDialogController.mm b/camino/src/bookmarks/AddBookmarkDialogController.mm index d8a8a39ca03..6077f559091 100644 --- a/camino/src/bookmarks/AddBookmarkDialogController.mm +++ b/camino/src/bookmarks/AddBookmarkDialogController.mm @@ -41,6 +41,8 @@ #import "Bookmark.h" #import "BookmarkManager.h" +#import "BookmarkViewController.h" + #import "AddBookmarkDialogController.h" @@ -254,7 +256,7 @@ NSString* const kAddBookmarkItemPrimaryTabKey = @"primary"; } } - [mBookmarkViewController revealItem:newItem selecting:YES]; + [mBookmarkViewController revealItem:newItem scrollIntoView:YES selecting:YES byExtendingSelection:NO]; [[BookmarkManager sharedBookmarkManager] setLastUsedBookmarkFolder:parentFolder]; } diff --git a/camino/src/bookmarks/BookmarkButton.mm b/camino/src/bookmarks/BookmarkButton.mm index 1a3310dcdc1..53a2be3122c 100644 --- a/camino/src/bookmarks/BookmarkButton.mm +++ b/camino/src/bookmarks/BookmarkButton.mm @@ -273,18 +273,19 @@ NSString *title = [item title]; if (isSingleBookmark) { - [pboard declareURLPasteboardWithAdditionalTypes:[NSArray arrayWithObject:@"MozBookmarkType"] owner:self]; + [pboard declareURLPasteboardWithAdditionalTypes:[NSArray arrayWithObject:kCaminoBookmarkListPBoardType] owner:self]; NSString *url = [(Bookmark *)item url]; NSString *cleanedTitle = [title stringByReplacingCharactersInSet:[NSCharacterSet controlCharacterSet] withString:@" "]; [pboard setDataForURL:url title:cleanedTitle]; } else { - [pboard declareTypes:[NSArray arrayWithObject:@"MozBookmarkType"] owner:self]; + [pboard declareTypes:[NSArray arrayWithObject:kCaminoBookmarkListPBoardType] owner:self]; } - // MozBookmarkType + + // kCaminoBookmarkListPBoardType NSArray *pointerArray = [BookmarkManager serializableArrayWithBookmarkItems:[NSArray arrayWithObject:item]]; - [pboard setPropertyList:pointerArray forType: @"MozBookmarkType"]; + [pboard setPropertyList:pointerArray forType: kCaminoBookmarkListPBoardType]; [self dragImage: [MainController createImageForDragging:[self image] title:title] at: NSMakePoint(0,NSHeight([self bounds])) offset: NSMakeSize(0,0) event: aEvent pasteboard: pboard source: self slideBack: YES]; @@ -295,7 +296,7 @@ if (operation == NSDragOperationDelete) { NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard]; - NSArray* bookmarks = [BookmarkManager bookmarkItemsFromSerializableArray:[pboard propertyListForType: @"MozBookmarkType"]]; + NSArray* bookmarks = [BookmarkManager bookmarkItemsFromSerializableArray:[pboard propertyListForType: kCaminoBookmarkListPBoardType]]; if (bookmarks) { for (unsigned int i = 0; i < [bookmarks count]; ++i) diff --git a/camino/src/bookmarks/BookmarkFolder.h b/camino/src/bookmarks/BookmarkFolder.h index 4b4441d5cc9..f27f48b8a44 100644 --- a/camino/src/bookmarks/BookmarkFolder.h +++ b/camino/src/bookmarks/BookmarkFolder.h @@ -99,7 +99,8 @@ enum { -(void) insertChild:(BookmarkItem *)aChild; -(void) insertChild:(BookmarkItem *)aChild atIndex:(unsigned)aIndex isMove:(BOOL)aBool; -(void) moveChild:(BookmarkItem *)aChild toBookmarkFolder:(BookmarkFolder *)aNewParent atIndex:(unsigned)aIndex; --(void) copyChild:(BookmarkItem *)aChild toBookmarkFolder:(BookmarkFolder *)aNewParent atIndex:(unsigned)aIndex; +// returns the new child +-(BookmarkItem*) copyChild:(BookmarkItem *)aChild toBookmarkFolder:(BookmarkFolder *)aNewParent atIndex:(unsigned)aIndex; // Used for deleting bookmarks/bookmark arrays -(BOOL) deleteChild:(BookmarkItem *)aChild; diff --git a/camino/src/bookmarks/BookmarkFolder.mm b/camino/src/bookmarks/BookmarkFolder.mm index 4fed14d54c4..2f56149e248 100644 --- a/camino/src/bookmarks/BookmarkFolder.mm +++ b/camino/src/bookmarks/BookmarkFolder.mm @@ -541,10 +541,11 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; [undoManager setActionName:NSLocalizedString(@"Move Separator",@"Move Separator")]; } --(void) copyChild:(BookmarkItem *)aChild toBookmarkFolder:(BookmarkFolder *)aNewParent atIndex:(unsigned)aIndex +-(BookmarkItem*) copyChild:(BookmarkItem *)aChild toBookmarkFolder:(BookmarkFolder *)aNewParent atIndex:(unsigned)aIndex { if ([aNewParent isRoot] && [aChild isKindOfClass:[Bookmark class]]) - return; + return nil; + BookmarkItem *copiedChild = [aChild copyWithZone:nil]; if (copiedChild) { [aNewParent insertChild:copiedChild atIndex:aIndex isMove:NO]; @@ -555,6 +556,7 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; else [undoManager setActionName:NSLocalizedString(@"Copy Bookmark",@"Copy Bookmark")]; } + return copiedChild; } // diff --git a/camino/src/bookmarks/BookmarkOutlineView.h b/camino/src/bookmarks/BookmarkOutlineView.h index 4ba3f8ae9f0..519fd3c17c9 100644 --- a/camino/src/bookmarks/BookmarkOutlineView.h +++ b/camino/src/bookmarks/BookmarkOutlineView.h @@ -44,4 +44,9 @@ @interface BookmarkOutlineView : ExtendedOutlineView { } + +// Actions for the edit menu +-(BOOL)validateMenuItem:(id)aMenuItem; +-(IBAction)delete:(id)aSender; + @end diff --git a/camino/src/bookmarks/BookmarkOutlineView.mm b/camino/src/bookmarks/BookmarkOutlineView.mm index 187ac227662..ed5476f1608 100644 --- a/camino/src/bookmarks/BookmarkOutlineView.mm +++ b/camino/src/bookmarks/BookmarkOutlineView.mm @@ -43,13 +43,13 @@ #import "BookmarkFolder.h" #import "Bookmark.h" #import "BookmarkManager.h" - +#import "NSPasteboard+Utils.h" @implementation BookmarkOutlineView - (void)awakeFromNib { - [self registerForDraggedTypes:[NSArray arrayWithObjects:@"MozURLType", @"MozBookmarkType", NSStringPboardType, NSURLPboardType, nil]]; + [self registerForDraggedTypes:[NSArray arrayWithObjects:kCaminoBookmarkListPBoardType, kWebURLsWithTitlesPboardType, NSStringPboardType, NSURLPboardType, nil]]; } -(NSMenu*)menu @@ -73,7 +73,7 @@ { if (operation == NSDragOperationDelete) { NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard]; - NSArray* bookmarks = [BookmarkManager bookmarkItemsFromSerializableArray:[pboard propertyListForType: @"MozBookmarkType"]]; + NSArray* bookmarks = [BookmarkManager bookmarkItemsFromSerializableArray:[pboard propertyListForType: kCaminoBookmarkListPBoardType]]; if (bookmarks) { for (unsigned int i = 0; i < [bookmarks count]; ++i) { BookmarkItem* item = [bookmarks objectAtIndex:i]; @@ -110,4 +110,32 @@ return (NSDragOperationDelete | NSDragOperationGeneric); } +// +// Override implementation in ExtendedOutlineView so we can check whether an +// item is selected or whether appropriate data is available on the clipboard. +// +- (BOOL)validateMenuItem:(id)aMenuItem +{ + SEL action = [aMenuItem action]; + + if (action == @selector(delete:)) + return [[self delegate] numberOfSelectedRows] > 0; + + if (action == @selector(copy:)) + return [super validateMenuItem:aMenuItem] && [[self delegate] numberOfSelectedRows] > 0; + + if (action == @selector(paste:)) + return [super validateMenuItem:aMenuItem] && [[self delegate] canPasteFromPasteboard:[NSPasteboard generalPasteboard]]; + + if (action == @selector(cut:)) + return NO; // XXX fix me [super validateMenuItem:aMenuItem] && [[self delegate] numberOfSelectedRows] > 0; + + return YES; +} + +- (IBAction)delete:(id)aSender +{ + [[self delegate] deleteBookmarks:aSender]; +} + @end diff --git a/camino/src/bookmarks/BookmarkToolbar.mm b/camino/src/bookmarks/BookmarkToolbar.mm index 53b00bb2e72..0f0357d29cc 100644 --- a/camino/src/bookmarks/BookmarkToolbar.mm +++ b/camino/src/bookmarks/BookmarkToolbar.mm @@ -76,7 +76,7 @@ static const int kBMBarScanningStep = 5; mDragInsertionButton = nil; mDragInsertionPosition = CHInsertNone; mDrawBorder = YES; - [self registerForDraggedTypes:[NSArray arrayWithObjects:@"MozURLType", @"MozBookmarkType", NSStringPboardType, NSURLPboardType, nil]]; + [self registerForDraggedTypes:[NSArray arrayWithObjects: kCaminoBookmarkListPBoardType, kWebURLsWithTitlesPboardType, NSStringPboardType, NSURLPboardType, nil]]; mIsShowing = YES; // Generic notifications for Bookmark Client NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; @@ -465,8 +465,8 @@ static const int kBMBarScanningStep = 5; if (!toolbar) return NO; - if ([types containsObject: @"MozBookmarkType"]) { - NSArray *draggedItems = [BookmarkManager bookmarkItemsFromSerializableArray:[draggingPasteboard propertyListForType: @"MozBookmarkType"]]; + if ([types containsObject: kCaminoBookmarkListPBoardType]) { + NSArray *draggedItems = [BookmarkManager bookmarkItemsFromSerializableArray:[draggingPasteboard propertyListForType: kCaminoBookmarkListPBoardType]]; BookmarkItem* destItem = nil; if (mDragInsertionButton == nil) { @@ -483,13 +483,9 @@ static const int kBMBarScanningStep = 5; if (![bmManager isDropValid:draggedItems toFolder:destItem]) return NO; } - else if ([types containsObject:NSStringPboardType]) { - // validate the string is a real url before allowing - NSURL* testURL = [NSURL URLWithString:[draggingPasteboard stringForType:NSStringPboardType]]; - return (testURL != nil); - } + else + return [draggingPasteboard containsURLData]; - return YES; } // NSDraggingDestination /////////// @@ -587,8 +583,8 @@ static const int kBMBarScanningStep = 5; NSArray *draggedTypes = [[sender draggingPasteboard] types]; - if ([draggedTypes containsObject:@"MozBookmarkType"]) { - NSArray *draggedItems = [BookmarkManager bookmarkItemsFromSerializableArray:[[sender draggingPasteboard] propertyListForType: @"MozBookmarkType"]]; + if ([draggedTypes containsObject:kCaminoBookmarkListPBoardType]) { + NSArray *draggedItems = [BookmarkManager bookmarkItemsFromSerializableArray:[[sender draggingPasteboard] propertyListForType: kCaminoBookmarkListPBoardType]]; // added sequentially, so use reverse object enumerator to preserve order. NSEnumerator *enumerator = [draggedItems reverseObjectEnumerator]; id aKid; @@ -600,27 +596,17 @@ static const int kBMBarScanningStep = 5; } dropHandled = YES; } - else if ([draggedTypes containsObject:@"MozURLType"]) { - NSDictionary* proxy = [[sender draggingPasteboard] propertyListForType: @"MozURLType"]; - [toolbar addBookmark:[proxy objectForKey:@"title"] url:[proxy objectForKey:@"url"] inPosition:index isSeparator:NO]; - dropHandled = YES; - } - else if ([draggedTypes containsObject:NSStringPboardType]) { - NSString* draggedText = [[sender draggingPasteboard] stringForType:NSStringPboardType]; - NSString* urlTitle = nil; - if ([draggedTypes containsObject:kCorePasteboardFlavorType_urld]) - urlTitle = [[sender draggingPasteboard] stringForType:kCorePasteboardFlavorType_urld]; - [toolbar addBookmark:(urlTitle ? urlTitle : draggedText) url:draggedText inPosition:index isSeparator:NO]; - dropHandled = YES; - } - else if ([draggedTypes containsObject: NSURLPboardType]) { - NSURL* urlData = [NSURL URLFromPasteboard:[sender draggingPasteboard]]; - NSString* urlTitle = nil; - if ([draggedTypes containsObject:kCorePasteboardFlavorType_urld]) - urlTitle = [[sender draggingPasteboard] stringForType:kCorePasteboardFlavorType_urld]; - [toolbar addBookmark:(urlTitle ? urlTitle : [urlData absoluteString]) url:[urlData absoluteString] inPosition:index isSeparator:NO]; + else if ([[sender draggingPasteboard] containsURLData]) { + NSArray* urls = nil; + NSArray* titles = nil; + [[sender draggingPasteboard] getURLs:&urls andTitles:&titles]; + + // Add in reverse order to preserve order + for ( int i = [urls count] - 1; i >= 0; --i ) + [toolbar addBookmark:[titles objectAtIndex:i] url:[urls objectAtIndex:i] inPosition:index isSeparator:NO]; dropHandled = YES; } + mDragInsertionButton = nil; mDragInsertionPosition = CHInsertNone; [self setNeedsDisplay:YES]; diff --git a/camino/src/bookmarks/BookmarkViewController.h b/camino/src/bookmarks/BookmarkViewController.h index 91e774c86f8..78c33e6ddb6 100644 --- a/camino/src/bookmarks/BookmarkViewController.h +++ b/camino/src/bookmarks/BookmarkViewController.h @@ -104,8 +104,8 @@ BOOL mBookmarkUpdatesDisabled; - NSMutableDictionary* mExpandedStatus; - NSString* mCachedHref; + NSMutableDictionary* mExpandedStates; + BookmarkFolder* mActiveRootCollection; BookmarkFolder* mRootBookmarks; NSArray* mSearchResultArray; @@ -133,6 +133,10 @@ -(IBAction) deleteBookmarks:(id)aSender; -(IBAction) showBookmarkInfo:(id)aSender; -(IBAction) locateBookmark:(id)aSender; +-(IBAction) cut:(id)aSender; +-(IBAction) copy:(id)aSender; +-(IBAction) paste:(id)aSender; +-(IBAction) delete:(id)aSender; -(IBAction) quicksearchPopupChanged:(id)aSender; - (void)resetSearchField; @@ -147,8 +151,8 @@ -(void) setActiveCollection:(BookmarkFolder *)aFolder; -(BookmarkFolder *)activeCollection; --(BookmarkFolder *)selectedItemFolderAndIndex:(int*)outIndex; --(void)revealItem:(BookmarkItem*)item selecting:(BOOL)inSelectItem; +- (BookmarkFolder *)selectedItemFolderAndIndex:(int*)outIndex; +- (void)revealItem:(BookmarkItem*)item scrollIntoView:(BOOL)inScroll selecting:(BOOL)inSelectItem byExtendingSelection:(BOOL)inExtendSelection; - (void)setItemToRevealOnLoad:(BookmarkItem*)inItem; @@ -157,4 +161,7 @@ -(void)completeSetup; -(void)ensureBookmarks; +-(BOOL) canPasteFromPasteboard:(NSPasteboard*)aPasteboard; +-(void) copyBookmarks:(NSArray*)bookmarkItemsToCopy toPasteboard:(NSPasteboard*)aPasteboard; + @end diff --git a/camino/src/bookmarks/BookmarkViewController.mm b/camino/src/bookmarks/BookmarkViewController.mm index e2e5cb07a3e..98456811ae7 100644 --- a/camino/src/bookmarks/BookmarkViewController.mm +++ b/camino/src/bookmarks/BookmarkViewController.mm @@ -25,6 +25,7 @@ * David Haas * Simon Woodside * Josh Aas + * Bruce Davidson * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -82,6 +83,8 @@ #define kGetInfoContextMenuItemTag 9 +static NSString* const kExpandedBookmarksStatesDefaultsKey = @"bookmarks_expand_state"; + // minimum sizes for the search panel const long kMinContainerSplitWidth = 150; const long kMinSearchPaneHeight = 80; @@ -123,6 +126,7 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; - (NSMutableDictionary *)expandedStateDictionary; - (void)restoreFolderExpandedStates; +- (void)saveExpandedStateDictionary; - (BOOL)hasExpandedState:(id)anItem; - (void)setStateOfItem:(BookmarkFolder *)anItem toExpanded:(BOOL)aBool; @@ -133,6 +137,8 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; - (NSDragOperation)preferredDragOperationForSourceMask:(NSDragOperation)srcMask; +-(void)pasteBookmarks:(NSPasteboard*)aPasteboard intoFolder:(BookmarkFolder *)dropFolder index:(int)index copying:(BOOL)isCopy; +-(void)pasteBookmarksFromURLsAndTitles:(NSPasteboard*)aPasteboard intoFolder:(BookmarkFolder*)dropFolder index:(int)index; @end @@ -172,6 +178,8 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; [[NSNotificationCenter defaultCenter] removeObserver:self]; + [self saveExpandedStateDictionary]; + // balance the extra retains [mBookmarksHostView release]; [mHistoryHostView release]; @@ -196,8 +204,7 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; // release data [mItemToReveal release]; - [mCachedHref release]; - [mExpandedStatus release]; + [mExpandedStates release]; [mActiveRootCollection release]; [mRootBookmarks release]; [mSearchResultArray release]; @@ -267,7 +274,7 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; [nc addObserver:self selector:@selector(managerStarted:) name:[BookmarkManager managerStartedNotification] object:nil]; // register for dragged types - [mContainersTableView registerForDraggedTypes:[NSArray arrayWithObjects:@"MozBookmarkType", @"MozURLType", NSURLPboardType, NSStringPboardType, nil]]; + [mContainersTableView registerForDraggedTypes:[NSArray arrayWithObjects:kCaminoBookmarkListPBoardType, kWebURLsWithTitlesPboardType, NSURLPboardType, NSStringPboardType, nil]]; [self ensureBookmarks]; @@ -582,6 +589,75 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; [mBookmarksOutlineView selectRow:[mBookmarksOutlineView rowForItem:item] byExtendingSelection:NO]; } +-(IBAction) cut:(id)aSender +{ + // XXX write me. We'll need to write to the pasteboard something other than an array of UUIDs, + // because we need to rip the bookmark items out of the tree. + +} + +- (IBAction)copy:(id)aSender +{ + // Get the list of bookmark items that are selected + NSMutableArray *bookmarkItemsToCopy = [NSMutableArray array]; + NSEnumerator* selRows = [mBookmarksOutlineView selectedRowEnumerator]; + id curSelectedRow; + while ((curSelectedRow = [selRows nextObject])) { + [bookmarkItemsToCopy addObject: [mBookmarksOutlineView itemAtRow:[curSelectedRow intValue]]]; + } + + [self copyBookmarks:bookmarkItemsToCopy toPasteboard:[NSPasteboard generalPasteboard]]; +} + + +// +// Paste bookmark(s) from the general pasteboard into the user's bookmarks file +// We use the view to work out where to paste the bookmark +// If no items are selected in the view : at the end of the bookmark menu folder +// If a folder is selected: at the end of that folder +// If a bookmark is selected: immediately after that bookmark, under the same parent +// XXX: At the moment if multiple items are selected we only examine the first one +// +-(IBAction) paste:(id)aSender +{ + NSArray* types = [[NSPasteboard generalPasteboard] types]; + + int pasteDestinationIndex = 0; + BookmarkFolder* pasteDestinationFolder = nil; + + // Work out what the selected item is and therefore where to paste the bookmark(s) + NSEnumerator* selRows = [mBookmarksOutlineView selectedRowEnumerator]; + id curSelectedRow = [selRows nextObject]; + + if (curSelectedRow) { + BookmarkItem* item = [mBookmarksOutlineView itemAtRow:[curSelectedRow intValue]]; + if ([item isKindOfClass:[BookmarkFolder class]]) { + pasteDestinationFolder = (BookmarkFolder*) item; + pasteDestinationIndex = [pasteDestinationFolder count]; + } else if ([item isKindOfClass:[Bookmark class]]) { + pasteDestinationFolder = (BookmarkFolder*) [item parent]; + pasteDestinationIndex = [pasteDestinationFolder indexOfObject:item] + 1; + } + } + + // If we don't have a destination use the end of the bookmark menu + if (!pasteDestinationFolder) { + pasteDestinationFolder = [[BookmarkManager sharedBookmarkManager] bookmarkMenuFolder]; + pasteDestinationIndex = [pasteDestinationFolder count]; + } + + // Do the actual copy based on the type available on the clipboard + if ([types containsObject: kCaminoBookmarkListPBoardType]) + [self pasteBookmarks:[NSPasteboard generalPasteboard] intoFolder:pasteDestinationFolder index:pasteDestinationIndex copying:YES]; + else if ([[NSPasteboard generalPasteboard] containsURLData]) + [self pasteBookmarksFromURLsAndTitles:[NSPasteboard generalPasteboard] intoFolder:pasteDestinationFolder index:pasteDestinationIndex]; +} + +-(IBAction) delete:(id)aSender +{ + [self deleteBookmarks:aSender]; +} + -(IBAction)quicksearchPopupChanged:(id)aSender { // do the search again (we'll pick up the new popup item tag) @@ -692,7 +768,7 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; mItemToReveal = [inItem retain]; } --(void)revealItem:(BookmarkItem*)item selecting:(BOOL)inSelectItem +-(void)revealItem:(BookmarkItem*)item scrollIntoView:(BOOL)inScroll selecting:(BOOL)inSelectItem byExtendingSelection:(BOOL)inExtendSelection { BookmarkManager* bmManager = [BookmarkManager sharedBookmarkManager]; @@ -774,9 +850,100 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; - (NSMutableDictionary *)expandedStateDictionary { - if (!mExpandedStatus) - mExpandedStatus = [[NSMutableDictionary alloc] initWithCapacity:20]; - return mExpandedStatus; + if (!mExpandedStates) + { + // We can't save BM expanded states to user defaults, because we don't have a persisent per-bookmark ID. + // mExpandedStates = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:kExpandedBookmarksStatesDefaultsKey] mutableCopy]; + // if (!mExpandedStates) + mExpandedStates = [[NSMutableDictionary alloc] initWithCapacity:20]; + } + return mExpandedStates; +} + +- (void)saveExpandedStateDictionary +{ + // We can't save BM expanded states to user defaults, because we don't have a persisent per-bookmark ID. + // if (mExpandedStates) + // [[NSUserDefaults standardUserDefaults] setObject:mExpandedStates forKey:kExpandedBookmarksStatesDefaultsKey]; +} + +-(void)pasteBookmarks:(NSPasteboard*)aPasteboard intoFolder:(BookmarkFolder *)dropFolder index:(int)index copying:(BOOL)isCopy +{ + NSArray* mozBookmarkList = [BookmarkManager bookmarkItemsFromSerializableArray:[aPasteboard propertyListForType: kCaminoBookmarkListPBoardType]]; + + NSMutableArray* newBookmarks = [[NSMutableArray alloc] initWithCapacity:[mozBookmarkList count]]; + if (!isCopy) + [newBookmarks addObjectsFromArray:mozBookmarkList]; + + // turn off updates to avoid lots of reloadData with multiple items + mBookmarkUpdatesDisabled = YES; + + // make sure we re-enable updates + NS_DURING + NSEnumerator *enumerator = [mozBookmarkList objectEnumerator]; + + id aKid; + while ((aKid = [enumerator nextObject])) + { + if (isCopy) + { + BookmarkItem* newItem = [[aKid parent] copyChild:aKid toBookmarkFolder:dropFolder atIndex:index]; + [newBookmarks addObject:newItem]; + ++index; + } + else + { + // need to be careful to adjust index as we insert items to avoid + // inserting in reverse order + if ([aKid parent] == dropFolder) + { + int kidIndex = [dropFolder indexOfObject:aKid]; + [[aKid parent] moveChild:aKid toBookmarkFolder:dropFolder atIndex:index]; + if (kidIndex > index) + ++index; + } + else + { + [[aKid parent] moveChild:aKid toBookmarkFolder:dropFolder atIndex:index]; + ++index; + } + } + } + NS_HANDLER + NS_ENDHANDLER + + mBookmarkUpdatesDisabled = NO; + [self reloadDataForItem:nil reloadChildren:YES]; + [self selectItems:newBookmarks expandingContainers:YES scrollIntoView:YES]; + [newBookmarks release]; +} + +-(void)pasteBookmarksFromURLsAndTitles:(NSPasteboard*)aPasteboard intoFolder:(BookmarkFolder *)dropFolder index:(int)index +{ + NSArray* urls = nil; + NSArray* titles = nil; + + [aPasteboard getURLs:&urls andTitles:&titles]; + + // turn off updates to avoid lots of reloadData with multiple items + mBookmarkUpdatesDisabled = YES; + + NSMutableArray* newBookmarks = [NSMutableArray arrayWithCapacity:[urls count]]; + // make sure we re-enable updates + NS_DURING + for ( unsigned int i = 0; i < [urls count]; ++i ) { + NSString* title = [titles objectAtIndex:i]; + if ([title length] == 0) + title = [urls objectAtIndex:i]; + + [newBookmarks addObject:[dropFolder addBookmark:title url:[urls objectAtIndex:i] inPosition:(index + i) isSeparator:NO]]; + } + NS_HANDLER + NS_ENDHANDLER + + mBookmarkUpdatesDisabled = NO; + [self reloadDataForItem:nil reloadChildren:YES]; + [self selectItems:newBookmarks expandingContainers:NO scrollIntoView:YES]; } // @@ -790,83 +957,17 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; NSArray* types = [[info draggingPasteboard] types]; BOOL isCopy = ([info draggingSourceOperationMask] == NSDragOperationCopy); - if ([types containsObject: @"MozBookmarkType"]) + if ([types containsObject: kCaminoBookmarkListPBoardType]) { - NSArray *draggedItems = [BookmarkManager bookmarkItemsFromSerializableArray:[[info draggingPasteboard] propertyListForType: @"MozBookmarkType"]]; - - // turn off updates to avoid lots of reloadData with multiple items - mBookmarkUpdatesDisabled = YES; - - // make sure we re-enable updates - NS_DURING - NSEnumerator *enumerator = [draggedItems objectEnumerator]; - - id aKid; - while ((aKid = [enumerator nextObject])) - { - if (isCopy) - { - [[aKid parent] copyChild:aKid toBookmarkFolder:dropFolder atIndex:index]; - ++index; - } - else - { - // need to be careful to adjust index as we insert items to avoid - // inserting in reverse order - if ([aKid parent] == dropFolder) - { - int kidIndex = [dropFolder indexOfObject:aKid]; - [[aKid parent] moveChild:aKid toBookmarkFolder:dropFolder atIndex:index]; - if (kidIndex > index) - ++index; - } - else - { - [[aKid parent] moveChild:aKid toBookmarkFolder:dropFolder atIndex:index]; - ++index; - } - } - } - NS_HANDLER - NS_ENDHANDLER - - mBookmarkUpdatesDisabled = NO; - [self reloadDataForItem:nil reloadChildren:YES]; - [self selectItems:draggedItems expandingContainers:NO scrollIntoView:NO]; - + [self pasteBookmarks:[info draggingPasteboard] intoFolder:dropFolder index:index copying:isCopy]; return YES; } - else if ([types containsObject: @"MozURLType"]) + + if ([[info draggingPasteboard] containsURLData]) { - NSDictionary* proxy = [[info draggingPasteboard] propertyListForType: @"MozURLType"]; - Bookmark* newBookmark = [dropFolder addBookmark:[proxy objectForKey:@"title"] url:[proxy objectForKey:@"url"] inPosition:index isSeparator:NO]; - [self selectItem:newBookmark expandingContainers:NO scrollIntoView:NO byExtendingSelection:NO]; + [self pasteBookmarksFromURLsAndTitles:[info draggingPasteboard] intoFolder:dropFolder index:index]; return YES; } - else if ([types containsObject: NSURLPboardType]) - { - NSURL* urlData = [NSURL URLFromPasteboard:[info draggingPasteboard]]; - NSString* urlTitle = nil; - if ([types containsObject:kCorePasteboardFlavorType_urld]) - urlTitle = [[info draggingPasteboard] stringForType:kCorePasteboardFlavorType_urld]; - Bookmark* newBookmark = [dropFolder addBookmark:(urlTitle ? urlTitle : [urlData absoluteString]) url:[urlData absoluteString] inPosition:index isSeparator:NO]; - [self selectItem:newBookmark expandingContainers:NO scrollIntoView:NO byExtendingSelection:NO]; - return YES; - } - else if ([types containsObject: NSStringPboardType]) - { - NSString* draggedText = [[info draggingPasteboard] stringForType:NSStringPboardType]; - NSURL *testURL = [NSURL URLWithString:draggedText]; - NSString* urlTitle = nil; - if ([types containsObject:kCorePasteboardFlavorType_urld]) - urlTitle = [[info draggingPasteboard] stringForType:kCorePasteboardFlavorType_urld]; - if (testURL) - { - Bookmark* newBookmark = [dropFolder addBookmark:(urlTitle ? urlTitle : draggedText) url:draggedText inPosition:index isSeparator:NO]; - [self selectItem:newBookmark expandingContainers:NO scrollIntoView:NO byExtendingSelection:NO]; - return YES; - } - } return NO; } @@ -883,6 +984,39 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; return NSDragOperationNone; } +// +// Copy a set of bookmarks (an NSArray containing the BookmarkItem and BookmarkFolder objects) +// to the specified pasteboard, in all the available formats +// +- (void) copyBookmarks:(NSArray*)bookmarkItemsToCopy toPasteboard:(NSPasteboard*)aPasteboard +{ + // Copy these items to the general pasteboard as an internal list so we can + // paste back to ourselves with no information loss + NSArray *bookmarkUUIDArray = [BookmarkManager serializableArrayWithBookmarkItems:bookmarkItemsToCopy]; + [aPasteboard declareTypes:[NSArray arrayWithObject:kCaminoBookmarkListPBoardType] owner:self]; + [aPasteboard setPropertyList:bookmarkUUIDArray forType:kCaminoBookmarkListPBoardType]; + + // Now add copies in formats useful to other applications. Our pasteboard + // category takes care of working out what formats to write. + NSMutableArray* urlList = [NSMutableArray array]; + NSMutableArray* titleList = [NSMutableArray array]; + for ( unsigned int i = 0; i < [bookmarkItemsToCopy count]; ++i ) { + BookmarkItem* bookmarkItem = [bookmarkItemsToCopy objectAtIndex:i]; + if ([bookmarkItem isKindOfClass:[Bookmark class]]) { + Bookmark* bookmark = (Bookmark*) bookmarkItem; + [urlList addObject:[bookmark url]]; + [titleList addObject:[bookmark title]]; + } + } + [aPasteboard setURLs:urlList withTitles:titleList]; +} + + +-(BOOL) canPasteFromPasteboard:(NSPasteboard*)aPasteboard +{ + return [[aPasteboard types] containsObject:kCaminoBookmarkListPBoardType] + || [aPasteboard containsURLData]; +} #pragma mark - // @@ -1016,11 +1150,9 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; [itemArray release]; return NO; } - // Pack pointers to bookmark items into this array - NSArray *pointerArray = [BookmarkManager serializableArrayWithBookmarkItems:itemArray]; + + [self copyBookmarks:itemArray toPasteboard:pboard]; [itemArray release]; - [pboard declareTypes:[NSArray arrayWithObject:@"MozBookmarkType"] owner:self]; - [pboard setPropertyList:pointerArray forType:@"MozBookmarkType"]; return YES; } @@ -1048,22 +1180,16 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; } if (dropFolder) { // special check if we're moving pointers around - if ([types containsObject:@"MozBookmarkType"]) + if ([types containsObject:kCaminoBookmarkListPBoardType]) { - NSArray *draggedItems = [BookmarkManager bookmarkItemsFromSerializableArray:[[info draggingPasteboard] propertyListForType: @"MozBookmarkType"]]; + NSArray *draggedItems = [BookmarkManager bookmarkItemsFromSerializableArray:[[info draggingPasteboard] propertyListForType: kCaminoBookmarkListPBoardType]]; BOOL isOK = [manager isDropValid:draggedItems toFolder:dropFolder]; return (isOK) ? dragOp : NSDragOperationNone; } - else if ([types containsObject:@"MozURLType"] || [types containsObject:NSURLPboardType]) + else if ([[info draggingPasteboard] containsURLData]) { return (dropFolder == mRootBookmarks) ? NSDragOperationNone : dragOp; } - else if ([types containsObject:NSStringPboardType]) - { - NSURL* testURL = [NSURL URLWithString:[[info draggingPasteboard] stringForType:NSStringPboardType]]; - if (testURL) - return (dropFolder == mRootBookmarks) ? NSDragOperationNone : dragOp; - } } } // nope @@ -1207,20 +1333,7 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; return NO; // Pack pointers to bookmark items into this array. - NSArray *pointerArray = [BookmarkManager serializableArrayWithBookmarkItems:items]; - if (count == 1) { - id aBookmark = [items objectAtIndex:0]; - if ([aBookmark isKindOfClass:[Bookmark class]]) { - [pboard declareURLPasteboardWithAdditionalTypes:[NSArray arrayWithObject:@"MozBookmarkType"] owner:self]; - [pboard setDataForURL:[aBookmark url] title:[aBookmark title]]; - [pboard setPropertyList:pointerArray forType:@"MozBookmarkType"]; - return YES; - } - } - // it's either a folder or we've got more than 1 thing. ship it - // out as MozBookmarkType - [pboard declareTypes:[NSArray arrayWithObject:@"MozBookmarkType"] owner: self]; - [pboard setPropertyList: pointerArray forType:@"MozBookmarkType"]; + [self copyBookmarks:items toPasteboard:pboard]; return YES; } @@ -1233,22 +1346,16 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; if (index == NSOutlineViewDropOnItemIndex) return NSDragOperationNone; - if ([types containsObject: @"MozBookmarkType"]) { - NSArray *draggedItems = [BookmarkManager bookmarkItemsFromSerializableArray:[[info draggingPasteboard] propertyListForType: @"MozBookmarkType"]]; + if ([types containsObject: kCaminoBookmarkListPBoardType]) { + NSArray *draggedItems = [BookmarkManager bookmarkItemsFromSerializableArray:[[info draggingPasteboard] propertyListForType: kCaminoBookmarkListPBoardType]]; BookmarkFolder* parent = (item) ? item : [self itemTreeRootContainer]; BOOL isOK = [[BookmarkManager sharedBookmarkManager] isDropValid:draggedItems toFolder:parent]; return (isOK) ? dragOp : NSDragOperationNone; } - if ([types containsObject: NSURLPboardType] || [types containsObject: @"MozURLType"] ) + if ([[info draggingPasteboard] containsURLData]) return dragOp; - // see if we can turn a string into a URL. If so, accept it. If not, punt. - if ([types containsObject: NSStringPboardType]) { - NSURL* testURL = [NSURL URLWithString:[[info draggingPasteboard] stringForType:NSStringPboardType]]; - if (testURL) - return dragOp; - } return NSDragOperationNone; } @@ -1483,6 +1590,8 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; // share code with revealItem:selecting: - (void)selectItem:(BookmarkItem*)item expandingContainers:(BOOL)expandContainers scrollIntoView:(BOOL)scroll byExtendingSelection:(BOOL)extendSelection { + [self revealItem:item scrollIntoView:scroll selecting:YES byExtendingSelection:extendSelection]; +#if 0 int itemRow = [mBookmarksOutlineView rowForItem:item]; if (itemRow == -1) { @@ -1496,6 +1605,7 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; [mBookmarksOutlineView selectRow:itemRow byExtendingSelection:extendSelection]; if (scroll) [mBookmarksOutlineView scrollRowToVisible:itemRow]; +#endif } - (id)itemTreeRootContainer @@ -1680,7 +1790,7 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; if (mItemToReveal) { - [self revealItem:mItemToReveal selecting:YES]; + [self revealItem:mItemToReveal scrollIntoView:YES selecting:YES byExtendingSelection:NO]; [mItemToReveal release]; mItemToReveal = nil; } diff --git a/camino/src/browser/BrowserTabBarView.mm b/camino/src/browser/BrowserTabBarView.mm index f9a7ecb0a32..f6da3006931 100644 --- a/camino/src/browser/BrowserTabBarView.mm +++ b/camino/src/browser/BrowserTabBarView.mm @@ -39,6 +39,7 @@ #import "BrowserTabBarView.h" #import "BrowserTabViewItem.h" #import "TabButtonCell.h" +#import "NSPasteboard+Utils.h" @interface BrowserTabBarView (PRIVATE) -(void)layoutButtons; @@ -81,8 +82,12 @@ static const int kOverflowButtonMargin = 1; mVisible = YES; // this will not likely have any result here [self rebuildTabBar]; - [self registerForDraggedTypes:[NSArray arrayWithObjects: @"MozURLType", @"MozBookmarkType", NSStringPboardType, - NSFilenamesPboardType, NSURLPboardType, nil]]; + [self registerForDraggedTypes:[NSArray arrayWithObjects: kCaminoBookmarkListPBoardType, + kWebURLsWithTitlesPboardType, + NSStringPboardType, + NSFilenamesPboardType, + NSURLPboardType, + nil]]; } return self; } diff --git a/camino/src/browser/BrowserTabView.mm b/camino/src/browser/BrowserTabView.mm index 881a05131f0..0fff1af9aea 100644 --- a/camino/src/browser/BrowserTabView.mm +++ b/camino/src/browser/BrowserTabView.mm @@ -91,7 +91,8 @@ mVisible = YES; [self showOrHideTabsAsAppropriate]; [self registerForDraggedTypes:[NSArray arrayWithObjects: - @"MozURLType", @"MozBookmarkType", NSStringPboardType, NSFilenamesPboardType, NSURLPboardType, nil]]; + kCaminoBookmarkListPBoardType, kWebURLsWithTitlesPboardType, + NSStringPboardType, NSFilenamesPboardType, NSURLPboardType, nil]]; } /******************************************/ @@ -367,8 +368,8 @@ // if there's no tabviewitem at the point within our view, check the tabbar as well. overTabViewItem = [mTabBar tabViewItemAtPoint:[sender draggingLocation]]; - if ([pasteBoardTypes containsObject:@"MozBookmarkType"]) { - NSArray *draggedItems = [BookmarkManager bookmarkItemsFromSerializableArray:[[sender draggingPasteboard] propertyListForType: @"MozBookmarkType"]]; + if ([pasteBoardTypes containsObject:kCaminoBookmarkListPBoardType]) { + NSArray *draggedItems = [BookmarkManager bookmarkItemsFromSerializableArray:[[sender draggingPasteboard] propertyListForType: kCaminoBookmarkListPBoardType]]; if (draggedItems) { id aBookmark; if ([draggedItems count] == 1) { @@ -393,13 +394,25 @@ } } } - else if ([pasteBoardTypes containsObject:@"MozURLType"]) { - // drag type is MozURLType - NSDictionary *data = [[sender draggingPasteboard] propertyListForType:@"MozURLType"]; - if (data) { - NSString *urlString = [data objectForKey:@"url"]; - return [self handleDropOnTab:overTabViewItem overContent:overContentArea withURL:urlString]; + else if ([[sender draggingPasteboard] containsURLData]) { + // Pasteboard contains a collection of URLs, handle in the same way we handle a collection of files + NSArray* urls; + NSArray* titles; + [[sender draggingPasteboard] getURLs:&urls andTitles:&titles]; + for (unsigned int i = 0; i < [urls count]; ++i) { + if (i == 0) { + // if we're over the content area, just load the first one + if (overContentArea) + return [self handleDropOnTab:overTabViewItem overContent:YES withURL:[urls objectAtIndex:i]]; + // otherwise load the first in the tab, and keep going + [self handleDropOnTab:overTabViewItem overContent:NO withURL:[urls objectAtIndex:i]]; + } + else { + // for subsequent items, make new tabs + [self handleDropOnTab:nil overContent:NO withURL:[urls objectAtIndex:i]]; + } } + return YES; } // check for NSFilenamesPboardType next so we always handle multiple filenames when we should else if ([pasteBoardTypes containsObject:NSFilenamesPboardType]) { NSArray *files = [[sender draggingPasteboard] propertyListForType:NSFilenamesPboardType]; @@ -433,14 +446,6 @@ } return YES; } - else if ([pasteBoardTypes containsObject:NSStringPboardType]) { - NSString *urlString = [[sender draggingPasteboard] stringForType: NSStringPboardType]; - return [self handleDropOnTab:overTabViewItem overContent:overContentArea withURL:urlString]; - } - else if ([pasteBoardTypes containsObject:NSURLPboardType]) { - NSURL *urlData = [NSURL URLFromPasteboard:[sender draggingPasteboard]]; - return [self handleDropOnTab:overTabViewItem overContent:overContentArea withURL:[urlData absoluteString]]; - } return NO; } diff --git a/camino/src/browser/BrowserTabViewItem.mm b/camino/src/browser/BrowserTabViewItem.mm index a699be6ea85..0a4dc3debe3 100644 --- a/camino/src/browser/BrowserTabViewItem.mm +++ b/camino/src/browser/BrowserTabViewItem.mm @@ -98,7 +98,8 @@ const int kMenuTruncationChars = 60; mTabButtonCell = [[TabButtonCell alloc] initFromTabViewItem:mTabViewItem]; [self registerForDraggedTypes:[NSArray arrayWithObjects: - @"MozURLType", @"MozBookmarkType", NSStringPboardType, NSFilenamesPboardType, NSURLPboardType, nil]]; + kCaminoBookmarkListPBoardType, kWebURLsWithTitlesPboardType, + NSStringPboardType, NSFilenamesPboardType, NSURLPboardType, nil]]; } return self; } diff --git a/camino/src/embedding/CHBrowserView.mm b/camino/src/embedding/CHBrowserView.mm index 31d2def15e4..bc1c1229e6b 100644 --- a/camino/src/embedding/CHBrowserView.mm +++ b/camino/src/embedding/CHBrowserView.mm @@ -95,13 +95,13 @@ typedef unsigned int DragReference; // Cut/copy/paste #include "nsIClipboardCommands.h" #include "nsIInterfaceRequestorUtils.h" +#import "NSPasteboard+Utils.h" // Undo/redo #include "nsICommandManager.h" #include "nsICommandParams.h" - const char kPersistContractID[] = "@mozilla.org/embedding/browser/nsWebBrowserPersist;1"; const char kDirServiceContractID[] = "@mozilla.org/file/directory_service;1"; @@ -160,7 +160,7 @@ const char kDirServiceContractID[] = "@mozilla.org/file/directory_service;1"; // register the view as a drop site for text, files, and urls. [self registerForDraggedTypes: [NSArray arrayWithObjects: - @"MozURLType", NSStringPboardType, NSURLPboardType, NSFilenamesPboardType, nil]]; + NSStringPboardType, NSURLPboardType, NSFilenamesPboardType, nil]]; // The value of mUseGlobalPrintSettings can't change during our lifetime. nsCOMPtr pref(do_GetService("@mozilla.org/preferences-service;1")); diff --git a/camino/src/extensions/ExtendedOutlineView.h b/camino/src/extensions/ExtendedOutlineView.h index 55cf67322a6..c56b8e8e5d3 100644 --- a/camino/src/extensions/ExtendedOutlineView.h +++ b/camino/src/extensions/ExtendedOutlineView.h @@ -81,6 +81,13 @@ - (void)setAutosaveTableSort:(BOOL)autosave; - (BOOL)autosaveTableSort; +// Clipboard functions +-(BOOL) validateMenuItem:(id)aMenuItem; +-(IBAction) copy:(id)aSender; +-(IBAction) delete:(id)aSender; +-(IBAction) paste:(id)aSender; +-(IBAction) cut:(id)aSender; + @end @interface NSObject (CHOutlineViewDataSourceToolTips) diff --git a/camino/src/extensions/ExtendedOutlineView.mm b/camino/src/extensions/ExtendedOutlineView.mm index b80c652466d..bcca9bce6cc 100644 --- a/camino/src/extensions/ExtendedOutlineView.mm +++ b/camino/src/extensions/ExtendedOutlineView.mm @@ -572,4 +572,48 @@ static NSString* const kAutosaveSortDirectionKey = @"sort_descending"; oldFrameRect = frameRect; } +#pragma mark - + +// Support clipboard actions if our delegate implements them +-(BOOL) validateMenuItem:(id)aMenuItem +{ + SEL action = [aMenuItem action]; + + // XXX we should probably try to call validateMenuItem: on the delegate too + if (action == @selector(delete:)) + return [[self delegate] respondsToSelector:@selector(delete:)]; + else if (action == @selector(copy:)) + return [[self delegate] respondsToSelector:@selector(copy:)]; + else if (action == @selector(paste:)) + return [[self delegate] respondsToSelector:@selector(paste:)]; + else if (action == @selector(cut:)) + return [[self delegate] respondsToSelector:@selector(cut:)]; + + return YES; +} + +-(IBAction) copy:(id)aSender +{ + if ([[self delegate] respondsToSelector:@selector(copy:)]) + [[self delegate] copy:aSender]; +} + +-(IBAction) paste:(id)aSender +{ + if ([[self delegate] respondsToSelector:@selector(paste:)]) + [[self delegate] paste:aSender]; +} + +-(IBAction) delete:(id)aSender +{ + if ([[self delegate] respondsToSelector:@selector(delete:)]) + [[self delegate] delete:aSender]; +} + +-(IBAction) cut:(id)aSender +{ + if ([[self delegate] respondsToSelector:@selector(cut:)]) + [[self delegate] cut:aSender]; +} + @end diff --git a/camino/src/extensions/NSPasteboard+Utils.h b/camino/src/extensions/NSPasteboard+Utils.h index 6009675d165..5c2f1e15285 100644 --- a/camino/src/extensions/NSPasteboard+Utils.h +++ b/camino/src/extensions/NSPasteboard+Utils.h @@ -20,6 +20,7 @@ * * Contributor(s): * Simon Fraser + * Bruce Davidson * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -41,11 +42,17 @@ extern NSString* const kCorePasteboardFlavorType_url; extern NSString* const kCorePasteboardFlavorType_urln; extern NSString* const kCorePasteboardFlavorType_urld; +extern NSString* const kCaminoBookmarkListPBoardType; +extern NSString* const kWebURLsWithTitlesPboardType; @interface NSPasteboard(ChimeraPasteboardURLUtils) -- (int)declareURLPasteboardWithAdditionalTypes:(NSArray*)additionalTypes owner:(id)newOwner; -- (void)setDataForURL:(NSString*)url title:(NSString*)title; +- (int) declareURLPasteboardWithAdditionalTypes:(NSArray*)additionalTypes owner:(id)newOwner; +- (void) setDataForURL:(NSString*)url title:(NSString*)title; + +- (void) setURLs:(NSArray*)inUrls withTitles:(NSArray*)inTitles; +- (void) getURLs:(NSArray**)outUrls andTitles:(NSArray**)outTitles; +- (BOOL) containsURLData; @end diff --git a/camino/src/extensions/NSPasteboard+Utils.mm b/camino/src/extensions/NSPasteboard+Utils.mm index 388e5a2f3d5..c18be37ac92 100644 --- a/camino/src/extensions/NSPasteboard+Utils.mm +++ b/camino/src/extensions/NSPasteboard+Utils.mm @@ -20,6 +20,7 @@ * * Contributor(s): * Simon Fraser + * Bruce Davidson * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -41,13 +42,16 @@ NSString* const kCorePasteboardFlavorType_url = @"CorePasteboardFlavorType 0x75 NSString* const kCorePasteboardFlavorType_urln = @"CorePasteboardFlavorType 0x75726C6E"; // 'urln' title NSString* const kCorePasteboardFlavorType_urld = @"CorePasteboardFlavorType 0x75726C64"; // 'urld' URL description +NSString* const kCaminoBookmarkListPBoardType = @"MozBookmarkType"; // list of Camino bookmark UIDs +NSString* const kWebURLsWithTitlesPboardType = @"WebURLsWithTitlesPboardType"; // Safari-compatible URL + title arrays + @implementation NSPasteboard(ChimeraPasteboardURLUtils) - (int)declareURLPasteboardWithAdditionalTypes:(NSArray*)additionalTypes owner:(id)newOwner { NSArray* allTypes = [additionalTypes arrayByAddingObjectsFromArray: [NSArray arrayWithObjects: - @"MozURLType", + kWebURLsWithTitlesPboardType, NSURLPboardType, NSStringPboardType, kCorePasteboardFlavorType_url, @@ -56,24 +60,125 @@ NSString* const kCorePasteboardFlavorType_urld = @"CorePasteboardFlavorType 0x75 return [self declareTypes:allTypes owner:newOwner]; } +// +// Copy a single URL (with an optional title) to the clipboard in all relevant +// formats. Convinience methods for clients that can only ever deal with one +// URL and shouldn't have to build up the arrays for setURLs:withTitles:. +// - (void)setDataForURL:(NSString*)url title:(NSString*)title { - NSDictionary *data = [NSDictionary dictionaryWithObjectsAndKeys: - url, @"url", - title, @"title", - nil]; - - [self setPropertyList:data forType: @"MozURLType"]; - [[NSURL URLWithString:url] writeToPasteboard: self]; - [self setString:url forType: NSStringPboardType]; - - const char* tempCString = [url UTF8String]; - [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType: kCorePasteboardFlavorType_url]; - + NSArray* urlList = [NSArray arrayWithObject:url]; + NSArray* titleList = nil; if (title) - tempCString = [title UTF8String]; - [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType: kCorePasteboardFlavorType_urln]; + titleList = [NSArray arrayWithObject:title]; + + [self setURLs:urlList withTitles:titleList]; } +// +// Copy a set of URLs, each of which may have a title, to the pasteboard +// using all the available formats. +// The title array should be nil, or must have the same length as the URL array. +// +- (void) setURLs:(NSArray*)inUrls withTitles:(NSArray*)inTitles +{ + // Best format that we know about is Safari's URL + title arrays - build these up + NSMutableArray* tmpTitleArray = inTitles; + if (!inTitles) { + tmpTitleArray = [NSMutableArray array]; + for ( unsigned int i = 0; i < [inUrls count]; ++i ) + [tmpTitleArray addObject:@""]; + } + + NSMutableArray* clipboardData = [NSMutableArray array]; + [clipboardData addObject:[NSArray arrayWithArray:inUrls]]; + [clipboardData addObject:tmpTitleArray]; + + [self setPropertyList:clipboardData forType:kWebURLsWithTitlesPboardType]; + + if ([inUrls count] == 1) { + NSString* title = @""; + if (inTitles) + title = [inTitles objectAtIndex:0]; + + NSString* url = [inUrls objectAtIndex:0]; + + [[NSURL URLWithString:url] writeToPasteboard: self]; + [self setString:url forType: NSStringPboardType]; + + const char* tempCString = [url UTF8String]; + [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType: kCorePasteboardFlavorType_url]; + + if (inTitles) + tempCString = [title UTF8String]; + [self setData:[NSData dataWithBytes:tempCString length:strlen(tempCString)] forType: kCorePasteboardFlavorType_urln]; + } else { + // With multiple URLs there aren't many other formats we can use + // Just write a string of each URL (ignoring titles) on a separate line + [self setString:[inUrls componentsJoinedByString:@"\n"] forType:NSStringPboardType]; + } +} + +// +// Get the set of URLs and their corresponding titles from the clipboards +// If there are no URLs in a format we understand on the pasteboard empty +// arrays will be returned. The two arrays will always be the same size. +// The arrays returned are on the auto release pool. +// +- (void) getURLs:(NSArray**)outUrls andTitles:(NSArray**)outTitles +{ + NSArray* types = [self types]; + if ([types containsObject:kWebURLsWithTitlesPboardType]) { + NSArray* urlAndTitleContainer = [self propertyListForType:kWebURLsWithTitlesPboardType]; + *outUrls = [urlAndTitleContainer objectAtIndex:0]; + *outTitles = [urlAndTitleContainer objectAtIndex:1]; + } else if ([types containsObject:NSURLPboardType]) { + *outUrls = [NSArray arrayWithObject:[NSURL URLFromPasteboard:self]]; + if ([types containsObject:kCorePasteboardFlavorType_urld]) + *outTitles = [NSArray arrayWithObject:[self stringForType:kCorePasteboardFlavorType_urld]]; + else + *outTitles = [NSArray arrayWithObject:@""]; + } else if ([types containsObject:NSStringPboardType]) { + NSURL* testURL = [NSURL URLWithString:[self stringForType:NSStringPboardType]]; + if (testURL) { + *outUrls = [NSArray arrayWithObject:[self stringForType:NSStringPboardType]]; + if ([types containsObject:kCorePasteboardFlavorType_urld]) + *outTitles = [NSArray arrayWithObject:[self stringForType:kCorePasteboardFlavorType_urld]]; + else + *outTitles = [NSArray arrayWithObject:@""]; + } else { + // The string doesn't look like a URL - return empty arrays + *outUrls = [NSArray array]; + *outTitles = [NSArray array]; + } + } else { + // We don't recognise any of these formats - return empty arrays + *outUrls = [NSArray array]; + *outTitles = [NSArray array]; + } +} + +// +// Indicates if this pasteboard contains URL data that we understand +// Deals with all our URL formats. Only strings that are valid URLs count. +// If this returns YES it is safe to use getURLs:andTitles: to retrieve the data. +// +// NB: Does not consider our internal bookmark list format, because callers +// usually need to deal with this separately because it can include folders etc. +// +- (BOOL) containsURLData +{ + NSArray* types = [self types]; + if ( [types containsObject:kWebURLsWithTitlesPboardType] + || [types containsObject:NSURLPboardType] ) + return YES; + + if ([types containsObject:NSStringPboardType]) { + NSURL* testURL = [NSURL URLWithString:[self stringForType:NSStringPboardType]]; + return (testURL != nil) && ([[testURL scheme] length] > 0); + } + + return NO; +} @end