Skip to content

Instantly share code, notes, and snippets.

@jerrykrinock
Created December 4, 2013 19:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jerrykrinock/7794404 to your computer and use it in GitHub Desktop.
Save jerrykrinock/7794404 to your computer and use it in GitHub Desktop.
This code is in response to a question on Stack Overflow wherein Jan Doggen asked if I could share my code. Probably won't be very helpful, but I'm all for sharing code, even if it happens to be Objective-C :) The general subject is: A general method for editing trees - methods which will add, remove, copy, move etc. nodes in the tree. Regarding…
#import <Cocoa/Cocoa.h>
#import "BkmxAppDel.h"
#import "BkmxGlobals.h"
enum BkmxParentingAction_enum
{
BkmxParentingActionNone, // 0
BkmxParentingAdd, // 1
BkmxParentingRemove, // 2
BkmxParentingCopy, // 3
BkmxParentingMoveOrCopy, // 4 Move if intra-document, Copy if inter-document
BkmxParentingMove // 5
// The opposite 'undo' actions are not needed since Core Data handles undo
} ;
typedef enum BkmxParentingAction_enum BkmxParentingAction ;
@class Stark ;
@class StarkLocation ;
@interface StarkEditor : NSObject {
}
/*!
@brief Efficiently removes many starks from their parents, adjusting sibling
indexes as necessary, removes any associated starxids, and removes
the starks from their managed object contexts
@param bkmxDoc If starks are being removed from a Bookmarkshelf, you must
pass it in here so that -[BkmxDoc postSortMaybeNot] will be invoked on it at
the end of this process. Also, progress will be indicated in the progress
view of the passed-in BkmxDoc. If you don't need either of those two tasks
done, or if there is no associated BkmxDoc, you may pass in nil.
*/
+ (void)removeStarks:(NSSet*)starks
bkmxDoc:(BkmxDoc*)bkmxDoc ;
/*!
@brief Attempts to obtain an array of subject starks from a given sender.
@details Attempts the following choices, in this order, returning
whichever first responds to the necessary selectors and returns a non-nil
answer:
<ol>
<li>If sender is itself a single stark, returns an array containing it</li>
<li>If sender is a collection, returns the collection.</li>
<li>If sender responds to selector -representedObject (which means it's
probably an NSMenuItem)...
<ol>
<li>If representedObject is a collection, returns the collection.</li>
<li>If representedObject responds to -selectedStarks (which means that
it is a window controller BkmxDocChildWindowController), returns those
-selectedStarks</li>
<li>If representedObject is a is a view in a window with a window
controller that responds to -selectedStarks (which means that the window
controller is a BkmxDocChildWindowController), returns those
-selectedStarks.</li>
</ol>
<li>If sender responds to -selectedStarks (which means that
it is a window controller BkmxDocChildWindowController), returns those
-selectedStarks</li>
<li>If sender is a view in a window with a window controller that
responds to -selectedStarks (which means that the window controller is
a BkmxDocChildWindowController), returns those -selectedStarks</li>
<li>Ignores sender.&nbsp; If the current -mainWindow of the
application is a window with a window controller that
responds to -selectedStarks (which means that the window controller is
a BkmxDocChildWindowController), returns those -selectedStarks</li>
</ol>
*/
+ (NSArray*)starksFromSender:(id)sender ;
+ (BOOL)canInsertStarks:(NSArray*)items intoParent:(Stark*)parent ;
/*!
@brief
@details Performs an action upon a subject collection of Stark instances
@param action The action to be performed
@param items An array of subject starks upon which to perform the action
@param newParent For add, move or copy operations, the parent into which
the subject item(s) should be added, moved or copied.
@param proposedIndex For add, move or copy operations, the index at which
the subject item(s) should be added in, moved to or copied to.
@param revealDestin If YES, will deselect whatever is selected in the user
interface, and instead select and reveal the subject starks after the move.
*/
+ (void)parentingAction:(BkmxParentingAction)action
items:(NSArray*)items
newParent:(Stark*)targetParent
newIndex:(NSInteger)proposedIndex
revealDestin:(BOOL)revealDestin ;
+ (void)moveItem:(id)item
toParent:(id)parentNew atIndex:(NSInteger)indexNew ;
+ (void)moveItem:(id)item
toLocation:(StarkLocation*)location ;
+ (void)moveAndSortNoRevealStarks:(NSArray*)starks
toNewParent:(Stark*)newParent ;
+ (void)copyAndSortNoRevealStarks:(NSArray*)starks
toNewParent:(Stark*)newParent ;
+ (void)mergeFolders:(NSArray*)folders
intoFolder:(Stark*)survivorFolder ;
+ (void)setSortableBool:(BOOL)yn
sender:(id)sender ;
+ (void)addTags:(NSArray*)newTags
toBookmarks:(NSArray*)bookmarks ;
+ (void)removeTags:(NSArray*)deletedTags
fromBookmarks:(NSArray*)bookmarks ;
+ (void)copyCut:(BOOL)cut
sender:(id)sender ;
+ (void)setSortDirectiveTopStark:(Stark*)item ;
+ (void)setSortDirectiveTopStarks:(NSArray*)starks ;
+ (void)setSortDirectiveBottomStark:(Stark*)item ;
+ (void)setSortDirectiveBottomStarks:(NSArray*)starks ;
+ (void)setSortDirectiveNormalStark:(Stark*)item ;
+ (void)setSortDirectiveNormalStarks:(NSArray*)starks ;
+ (void)visitStark:(Stark*)stark ;
+ (void)visitItems:(NSArray*)items
urlTemporality:(BkmxUrlTemporality)urlTemporality ;
+ (void)swapPriorAndCurrentUrls:(id)sender ;
+ (void)swapSuggestedAndCurrentUrls:(id)sender ;
@end
#import "StarkEditor.h"
#import "BkmxDoc.h"
#import "NSArray+Starks.h"
#import "Stark+Sorting.h"
#import "Stark+Attributability.h"
#import "Starker.h"
#import "BkmxAppDel+Actions.h"
#import "BkmxAppDel+Capabilities.h"
#import "BkmxBasis+Strings.h"
#import "SSWebBrowsing+Bkmx.h"
#import "ContentOutlineView.h"
#import "BkmxArrayCategories.h"
#import "NSString+LocalizeSSY.h"
#import "SSYProgressView.h"
#import "BkmxDocWinCon.h"
@implementation StarkEditor
+ (void)removeStarks:(NSSet*)starks
bkmxDoc:(BkmxDoc*)bkmxDoc {
if ([starks count] > 0) {
SSYProgressView* progressView = [bkmxDoc progressView] ;
NSMutableSet* affectedParents = [[NSMutableSet alloc] init] ;
// Phase 1. Delete the starks
[progressView setIndeterminate:NO
withLocalizedVerb:[[NSString localizeFormat:
@"deleting%0",
[NSString localize:@"items"]] lowercaseString]] ;
[progressView setMaxValue:[starks count]] ;
[progressView setDoubleValue:1] ;
for (Stark* stark in starks) {
// Local autorelease pool added to improve performance, BookMacster version 1.3.19
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init] ;
Stark* parent = [stark parent] ;
if (parent) {
[affectedParents addObject:parent] ;
}
// If a stark is being removed, we no longer need to restack it in Phase 2
[affectedParents removeObject:stark] ;
[stark moveToBkmxParent:nil
atIndex:NSNotFound
restack:NO] ;
[pool drain] ;
[progressView incrementDoubleValueBy:1] ;
}
[progressView clearAll] ;
// Phase 2. Restack the parent(s)
[progressView setIndeterminate:NO
withLocalizedVerb:@"Restacking siblings"] ;
[progressView setMaxValue:[affectedParents count]] ;
[progressView setDoubleValue:1] ;
for (Stark* stark in affectedParents) {
// When we do this stealthily, we must -postSortMaybeNot to bkmxDoc later
[stark restackChildrenStealthily:YES] ;
[progressView incrementDoubleValueBy:1] ;
}
[affectedParents release] ;
[progressView clearAll] ;
[bkmxDoc postSortMaybeNot] ;
}
}
+ (NSArray*)starksFromSender:(id)sender {
NSArray* starks = nil ;
// Choice 1: sender itself is a single stark
if ([sender isKindOfClass:[Stark class]]) {
starks = [NSArray arrayWithObject:self] ;
}
// Choice 2: sender is the collection of starks
if ([sender respondsToSelector:@selector(count)]) {
starks = sender ;
}
// Choice 3: sender is an NSMenuItem.
if ([sender respondsToSelector:@selector(representedObject)]) {
// sender is an NSMenuItem from a contextual menu
id object = [(NSMenuItem*)sender representedObject] ;
// Choice 3a: representedObject is the collection of starks
if ([object respondsToSelector:@selector(count)]) {
starks = object ;
}
// Choice 3b: representedObject is a BkmxDocChildWindowController
else if ([object respondsToSelector:@selector(selectedStarks)]) {
starks = [(BkmxDocWinCon*)object selectedStarks] ;
}
// Choice 3c: representedObject is a view in a window controller by
// a BkmxDocChildWindowController
else if ([object respondsToSelector:@selector(window)]) {
object = [[(NSWindowController*)object window] windowController] ;
if ([(NSWindowController*)object respondsToSelector:@selector(selectedStarks)]) {
starks = [(BkmxDocWinCon*)object selectedStarks] ;
}
}
}
// Choice 4: sender is a BkmxDocChildWindowController.
if ([sender respondsToSelector:@selector(selectedStarks)]) {
starks = [(BkmxDocWinCon*)sender selectedStarks] ;
}
// Choice 5: sender is an NSView.
if (!starks) {
if ([sender respondsToSelector:@selector(window)]) {
NSWindowController* windowController = [[(NSView*)sender window] windowController] ;
if ([windowController respondsToSelector:@selector(selectedStarks)]) {
starks = [(BkmxDocWinCon*)windowController selectedStarks] ;
}
}
}
// Choice 6: sender is nil, or something that is useless for our purposes.
if (!starks) {
NSWindow* window = [NSApp mainWindow] ;
NSWindowController* windowController = [window windowController] ;
if ([windowController respondsToSelector:@selector(selectedStarks)]) {
starks = [(BkmxDocWinCon*)windowController selectedStarks] ;
}
}
return starks ;
}
+ (BOOL)canInsertStarks:(NSArray*)items
intoParent:(Stark*)parent {
StarkLocation* location = [StarkLocation starkLocationWithParent:parent
index:NSNotFound] ;
return [location canInsertItems:items
fixParent:NO
fixIndex:NO
outlineView:nil] ;
}
+ (void)parentingAction:(BkmxParentingAction)action
items:(NSArray*)items
newParent:(Stark*)newParent
newIndex:(NSInteger)newIndex
revealDestin:(BOOL) revealDestin {
#if 0
#warning Logging parentingAction:::::
NSLog(
@"->\n"
@"parentingAction: %ld\n"
@" items: %@\n"
@" newParent: %@\n"
@" newIndex: %ld\n",
(long)action,
[items listValuesForKey:constKeyName
conjunction:nil
truncateTo:0],
[newParent shortDescription],
(long)newIndex
) ;
#endif
// Original algorithm design for this method was in document "ParentingActions.xls"
// Examine the two "new" id arguments, which come elsewhere, and make them into single-object arrays
// if they are non-nil and not arrays.
Class arrayClass = [NSArray class] ;
if (items && ![items isKindOfClass:arrayClass]) {
items = [NSArray arrayWithObject:items] ;
}
// Assign refs needed for bkmxDocSource and bkmxDocDestin, coming up after this.
BkmxDoc* bkmxDocSource = nil ;
if ([items count]) {
bkmxDocSource = (BkmxDoc*)[[items objectAtIndex:0] owner] ;
}
BkmxDoc* bkmxDocDestin = (BkmxDoc*)[newParent owner] ;
// Changed in BookMacster 1.14
// If action is MoveOrCopy, resolve it into one or the other
if (action == BkmxParentingMoveOrCopy) {
if (bkmxDocSource != bkmxDocDestin) {
// inter-document
action = BkmxParentingCopy ;
}
else {
// intra-document
action = BkmxParentingMove ;
}
}
// Below here, action BkmxParentingMoveOrCopy need not be considered.
// Assign data arrays for itemsActuallyDelivered for selecting at the end
NSMutableArray* itemsActuallyDelivered = nil ;
if ((action == BkmxParentingAdd) || (action == BkmxParentingCopy) || (action == BkmxParentingMove)) {
itemsActuallyDelivered = [[NSMutableArray alloc] init] ;
}
// At the head of Loop 1 below, immoveable containers will be ignored.
// But we don't want their children to be ignored, so we add them
// to 'items' directly
items = [items arrayOfStarksByPromotingChildrenOfImmoveableContainers] ;
/* Well, there are two possible algorithms to do this. We have two arrays to inspect,
'items' and 'copies', for objects to "move" and "remove". Algorithm "A" is to
loop through 'items', moving and removing on the fly, then loop through 'copies'
and do the same thing. Algorithm "B" is to (1) loop through 'items', adding to the
following four arrays the objects to move, where to move them, and the objects
to remove, and then (2) loop through 'copies', adding more to these arrays,
then (3) remove the collected removees and finally (4) move the collected movees.
I first did Algorithm "A", since most of the effort is in the collecting,
the moveeTargetIndexes would get to be a mess and alot of unnecessary processing
when you have to increment them, and also the loop through copies is rather easy
since these can only be removed; they are never moved. But then I realized that,
in order for indexes to be preserved, "moving" must be done in forward order, but
"removing" must be done in reverse order. So, we now use Algorithm "B". */
// Arrays for collecting objects for moving and removing
NSMutableArray* moves = [[NSMutableArray alloc] init] ;
NSMutableArray* removees = [[NSMutableArray alloc] init] ;
// Variables to be used in loops
Stark* movee ;
Stark* removee ;
StarkLocation* moveeTargetStarkLocation ;
NSInteger moveeTargetIndex ;
// Loop 1 of 4. Loop through 'items'. Make copies and collect movees, their targets, and removees
// Alloc init an array to be used to make sure that we don't repeat
// items already processed.
NSMutableArray* itemsAlreadyProcessed = [[NSMutableArray alloc] init] ;
NSInteger i = 0 ;
for (Stark* item in items) {
// If a user selects a child and its parent, it might be
// in 'items' twice, which means that it would be deleted
// from the managed object context twice, for example.
// We now filter those out.
if ([itemsAlreadyProcessed containsObject:item]) {
continue ;
}
// Immoveable containers get filtered out too. (We have
// previously "promoted" their children.
if (![item isMoveable]) {
continue ;
}
NSMutableArray* itemAndDescendants = [[NSMutableArray alloc] init] ;
[item classifyBySharypeDeeply:YES
hartainers:nil // These should already have been removed
softFolders:itemAndDescendants
leaves:itemAndDescendants
notches:itemAndDescendants] ;
[itemsAlreadyProcessed addObjectsFromArray:itemAndDescendants] ;
[itemAndDescendants release] ;
moveeTargetIndex = newIndex ;
// Retarget newParent and moveeTargetIndex if needed
StarkLocation* location ;
if (newParent) {
location = [StarkLocation starkLocationWithParent:newParent
index:moveeTargetIndex] ;
[location canInsertItems:[NSArray arrayWithObject:item]
fixParent:YES
fixIndex:YES
outlineView:nil] ;
newParent = [location parent] ;
}
else {
location = nil ;
}
NSInteger fixedMoveeTargetIndex = [location index] ;
// Assign movee and removee, making freshCopy if needed
movee = nil ;
removee = nil ;
Stark* freshCopy = nil ;
switch (action) {
case BkmxParentingRemove:
removee = item ;
break ;
case BkmxParentingCopy:
// We need a fresh copy since the copied item must have new exids and addDate
freshCopy = [item copyDeepOrphanedFreshened:YES
replicator:bkmxDocDestin] ; // copy, not autoreleased
[freshCopy autorelease] ;
movee = freshCopy ;
break ;
case BkmxParentingMove:
case BkmxParentingAdd:
movee = item ;
break ;
case BkmxParentingActionNone:
NSLog(@"Internal Error 215-0484") ;
break;
case BkmxParentingMoveOrCopy:
NSLog(@"Internal Error 215-0483") ;
break ;
}
// Add removee if one was generated
if (removee) {
[removees addObject:removee] ;
}
// Prior to BookMacster 1.11.4, movee and newParent were tested
// for nonnilness and processed separately, which, if movee was non-nil and
// newParent was nil, allowed a move to be inserted which had nil
// parent in its moveeTargetStarkLocation, which was useless, and also
// caused an exception to be raised later, at Note 20120614. This would
// happen if action was type BkmxParentingMove and newParent was nil,
// which is actually an error that we made. Testing both for nonnnilness
// before processing either is therefor for
// (1) efficiency (2) common sense (3) defensive programming.
moveeTargetStarkLocation = nil ;
if (newParent && movee) {
moveeTargetStarkLocation = [[StarkLocation alloc] initWithParent:newParent
index:fixedMoveeTargetIndex] ;
// Noted in development of BookMacster 1.11.4: Why is it necessary to
// send the following message *again*?
[moveeTargetStarkLocation canInsertItems:nil
fixParent:YES
fixIndex:NO
outlineView:nil] ;
// Do not involve outlineView because we will be dropping other items
// ahead of this one, so the range may not be OK now but it
// it will be at the time we insert it. We do not want our
// target index to be changed due to out of range.
// Add move info to collection 'moves'
NSMutableDictionary* move = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
movee, @"movee", nil] ;
if (moveeTargetStarkLocation) {
[move setObject:moveeTargetStarkLocation
forKey:@"moveeTargetStarkLocation"] ;
}
[moveeTargetStarkLocation release] ; // Memory leak fixed in BookMacster 1/17
[moves addObject:move] ;
[move release] ;
}
i++ ;
}
[itemsAlreadyProcessed release] ;
// Loop 2 of 4. Move the movees.
NSMutableSet* affectedParents = [[NSMutableSet alloc] init] ;
NSMutableDictionary* displacedIndexSets = [[NSMutableDictionary alloc] init] ;
for (NSDictionary* move in moves) {
// Unpack the move
movee = [move objectForKey:@"movee"] ;
moveeTargetStarkLocation = [move objectForKey:@"moveeTargetStarkLocation"] ;
Stark* moveeTargetParent = [moveeTargetStarkLocation parent] ;
moveeTargetIndex = [moveeTargetStarkLocation index] ;
Stark* oldParent = [movee parent] ;
if (oldParent) {
[affectedParents addObject:oldParent] ;
}
NSInteger adjustedTargetIndex = moveeTargetIndex ;
NSMutableIndexSet* displacedIndexSet = [displacedIndexSets objectForKey:[moveeTargetParent objectID]] ;
if (displacedIndexSet) {
NSInteger lastUsedIndexForThisParent = [displacedIndexSet lastIndex] ;
if (adjustedTargetIndex <= lastUsedIndexForThisParent) {
adjustedTargetIndex = ++lastUsedIndexForThisParent ;
}
[displacedIndexSet addIndex:adjustedTargetIndex] ;
}
else {
displacedIndexSet = [NSMutableIndexSet indexSetWithIndex:adjustedTargetIndex] ;
}
// Note 20120614
[displacedIndexSets setObject:displacedIndexSet
forKey:[moveeTargetParent objectID]] ;
[movee setDonkey:1] ; // donkey=1 means that this was child was moved
[affectedParents addObject:moveeTargetParent] ;
movee = [movee moveToBkmxParent:moveeTargetParent
atIndex:adjustedTargetIndex
restack:NO] ;
// Note: If moving to a different managed object context (i.e. a different
// BkmxDoc), the above will assign a new movee inserted in to the new moc.
[itemsActuallyDelivered addObject:movee] ;
}
// Loop 3 of 4. Remove the removees
for (removee in removees) {
Stark* parent = [removee parent] ;
if (parent) {
[affectedParents addObject:parent] ;
}
[removee moveToBkmxParent:nil
atIndex:0
restack:NO] ;
}
// Loop 4 of 4. Restack children in affected parents
for (Stark* aParent in affectedParents) {
NSIndexSet* displacedIndexSet = [displacedIndexSets objectForKey:[aParent objectID]] ;
if (displacedIndexSet) {
NSInteger firstDisplacedIndex = [displacedIndexSet firstIndex] ;
NSInteger lastDisplacedIndex = [displacedIndexSet lastIndex] ;
NSInteger offset = lastDisplacedIndex - firstDisplacedIndex + 1 ;
if (offset > 0) {
for (Stark* child in [aParent children]) {
if ([child donkey] == 0) {
// This child was not one of the movees; it was not moved
if ([child indexValue] >= firstDisplacedIndex) {
#if USE_OPTIMIZED_RESTACK_CHILDREN
[child stealthilySetIndex:[NSNumber numberWithInteger:([child indexValue] + offset)]] ;
// Sending -[BkmxDoc postSortMaybeNot] as required by this optimization
// is done by -restackChildrenStealthily:, which will be invoked soon.
#else
[child setIndexValue:([child indexValue] + offset)] ;
#endif
}
}
else {
[child setDonkey:0] ;
}
}
}
}
[aParent restackChildrenStealthily:YES] ;
}
[affectedParents release] ;
[displacedIndexSets release] ;
// Set Undo Action(s)
if ([removees count] > 0) {
[bkmxDocSource setUndoActionNameForAction:SSYModelChangeActionRemove
object:nil
objectKey:nil
updatedKey:nil
count:[removees count]] ;
if ([moves count] > 0) {
NSLog(@"Internal Error 128-3850 Move & Remove") ;
}
}
else if ([moves count] > 0) {
if (bkmxDocSource == bkmxDocDestin) {
// Move within same document
[bkmxDocSource setUndoActionNameForAction:SSYModelChangeActionMove
object:nil
objectKey:nil
updatedKey:nil
count:[moves count]] ;
}
else {
// Move from one document to another.
#if 000
// Source has items removed, destin has items added
[bkmxDocSource setUndoActionNameForAction:SSYModelChangeActionRemove
object:nil
objectKey:nil
updatedKey:nil
count:[moves count]] ;
#endif
// Destin has items added
[bkmxDocDestin setUndoActionNameForAction:SSYModelChangeActionInsert
object:nil
objectKey:nil
updatedKey:nil
count:[moves count]] ;
}
}
[moves release] ;
[removees release] ;
BkmxDocWinCon* augmentedMWC = [bkmxDocDestin bkmxDocWinCon] ;
BkmxDocWinCon* diminishedMWC = [bkmxDocSource bkmxDocWinCon] ;
[augmentedMWC reloadAll] ;
[diminishedMWC reloadAll] ;
if (revealDestin) {
// Deselect moved items in bkmxDocSource
// This section only does anything if we are doing a move or remove,
// because, by design (above), if we are doing an add or copy, bkmxDocSource = nil.
[diminishedMWC deselectAll] ;
// Show and Select new items in bkmxDocDestin
[augmentedMWC deselectAll] ;
BOOL itemsToShow = (
(newParent != nil)
&&
(
(action == BkmxParentingAdd)
||
(action == BkmxParentingCopy)
||
(action == BkmxParentingMove)
)
) ;
[[augmentedMWC contentOutlineView] showItems:itemsActuallyDelivered
selectThem:itemsToShow
expandAsNeeded:YES] ;
}
[itemsActuallyDelivered release] ;
end:
return ;
}
+ (void)moveItem:(id)item
toParent:(id)parent
atIndex:(NSInteger)indexNew {
[self parentingAction:BkmxParentingMove
items:[NSArray arrayWithObject:item]
newParent:parent
newIndex:indexNew
revealDestin:YES] ;
}
+ (void)moveItem:(id)item
toLocation:(StarkLocation*)location {
[self moveItem:item
toParent:[location parent]
atIndex:[location index] ] ;
}
+ (void)moveAndSortNoRevealStarks:(NSArray*)starks
toNewParent:(Stark*)newParent {
[self parentingAction:BkmxParentingMove
items:starks
newParent:newParent
newIndex:NSNotFound
revealDestin:NO] ;
[newParent sortShallowly] ;
}
+ (void)mergeFolders:(NSArray*)folders
intoFolder:(Stark*)survivorFolder {
for (Stark* folder in folders) {
[self parentingAction:BkmxParentingMove
items:[folder childrenOrdered]
newParent:survivorFolder
newIndex:NSNotFound
revealDestin:NO] ;
}
[self parentingAction:BkmxParentingRemove
items:folders
newParent:nil
newIndex:0
revealDestin:NO] ;
[survivorFolder sortShallowly] ;
[[(BkmxDoc*)[survivorFolder owner] undoManager] setActionName:[NSString localize:@"mergeInto"]] ;
}
+ (void)copyAndSortNoRevealStarks:(NSArray*)starks
toNewParent:(Stark*)newParent {
[self parentingAction:BkmxParentingCopy
items:starks
newParent:newParent
newIndex:NSNotFound
revealDestin:NO] ;
[newParent sortShallowly] ;
}
+ (void)setSortableBool:(BOOL)yn
sender:(id)sender {
NSArray* starks = [StarkEditor starksFromSender:sender] ;
if ([starks count] > 0) {
BkmxDoc* bkmxDoc = [[starks objectAtIndex:0] owner] ;
[[bkmxDoc starker] setValue:[NSNumber numberWithBool:yn]
forKey:constKeySortable
inStarks:starks] ;
}
}
+ (void)addTags:(NSArray*)newTags
toBookmarks:(NSArray*)bookmarks {
for (Stark* item in bookmarks) {
NSArray* oldTags = [item tags] ;
NSArray* combinedTags ;
if ([oldTags count] > 0) {
combinedTags = [oldTags arrayByAddingObjectsFromArray:newTags] ;
}
else {
combinedTags = newTags ;
}
[item setTags:combinedTags] ;
}
}
+ (void)removeTags:(NSArray*)deletedTags
fromBookmarks:(NSArray*)bookmarks {
for (Stark* item in bookmarks) {
NSArray* tags = [item tags] ;
if (([tags count] > 0) && ([deletedTags count] > 0)) {
NSMutableArray* newTags = [tags mutableCopy] ;
for (NSString* deletedTag in deletedTags) {
[newTags removeObject:deletedTag] ;
}
NSArray* newTagsCopy = [newTags copy] ;
[newTags release] ;
[item setTags:newTagsCopy] ;
[newTagsCopy release] ;
}
}
}
+ (void)copyCut:(BOOL)cut
sender:(id)sender {
NSArray* starks = [self starksFromSender:sender] ;
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSArray* providedDragTypes = [NSArray arrayWithObjects:constBkmxPboardTypeStark, NSStringPboardType, nil] ;
[pasteboard declareTypes:providedDragTypes owner:nil];
// Above, we can say owner:nil since we are going to provide data immediately
// For pasting to other apps, copy tab/return name/urls of starks
NSString* urls = [starks tabReturnLeavesSummary] ;
if (urls) {
[pasteboard setString:urls forType:NSStringPboardType];
}
// For pasting to ourselves, we copy all selected starks to intraAppCopyArray
// The following does not work because of the @"Parent" key in each item, which is an NSValue.
// NSValues are not supported for serialization. Therefore, even though setPropertyLost:forType will return YES,
// it is in fact not successful. The pasteboard will contain NULL for type constBkmxPboardTypeStark
// success1 = [pboard setPropertyList:starks forType:constBkmxPboardTypeStark] ;
// So, I do this cheesy method instead:
[[[NSApp delegate] intraAppCopyArray] removeAllObjects] ;
[[[NSApp delegate] intraAppCopyArray] addObjectsFromArray:starks] ;
if (cut) {
[self parentingAction:BkmxParentingRemove
items:starks
newParent:nil
newIndex:0
revealDestin:YES] ;
}
}
+ (void)setSortDirectiveTopStark:(Stark*)stark {
[stark setSortDirectiveValue:BkmxSortDirectiveTop] ;
}
+ (void)setSortDirectiveTopStarks:(NSArray*)starks {
if ([starks count] > 0) {
for (Stark* stark in starks) {
[self setSortDirectiveTopStark:stark] ;
}
}
}
+ (void)setSortDirectiveBottomStark:(Stark*)stark {
[stark setSortDirectiveValue:BkmxSortDirectiveBottom] ;
}
+ (void)setSortDirectiveBottomStarks:(NSArray*)starks {
if ([starks count] > 0) {
for (Stark* stark in starks) {
[self setSortDirectiveBottomStark:stark] ;
}
}
}
+ (void)setSortDirectiveNormalStark:(Stark*)stark {
[stark setSortDirectiveValue:BkmxSortDirectiveNormal] ;
}
+ (void)setSortDirectiveNormalStarks:(NSArray*)starks {
if ([starks count] > 0) {
for (Stark* stark in starks) {
[self setSortDirectiveNormalStark:stark] ;
}
}
}
+ (void)visitItems:(NSArray*)items
urlTemporality:(BkmxUrlTemporality)urlTemporality {
// Build array of bookmarks included in the selectedObjects (which may include folders)
// Instead of this, could save bookmarks as an ivar when building menu.
NSMutableArray* bookmarks = [[NSMutableArray alloc] init] ;
for (Stark* stark in items) {
[stark classifyBySharypeDeeply:NO
hartainers:nil
softFolders:nil
leaves:bookmarks
notches:nil] ;
}
// Count them
NSInteger count = [bookmarks count] ;
if (count > 0) {
// If too many ask user if really want to do this
NSInteger alertReturn = NSAlertDefaultReturn ;
if (count > [[NSUserDefaults standardUserDefaults] integerForKey:constKeyVisitWarningThreshold]) {
SSYAlert* alert = [SSYAlert alert] ;
[alert setTitleText:[NSString localize:@"areYouSure"]] ;
[alert setSmallText:[NSString stringWithFormat:[NSString localize:@"visitNWebPages"], (long)count]] ;
[alert setButton1Title:[NSString localize:@"yes"]] ;
[alert setButton2Title:[NSString localize:@"cancel"]] ;
[alert setCheckboxTitle:@"Open Preferences so I can adjust this warning."] ;
[alert runModalDialog] ;
alertReturn = [alert alertReturn] ;
if ([alert checkboxState] == NSOnState) {
[[NSApp delegate] preferences:self] ;
PrefsWinCon* prefsWinCon = [[NSApp delegate] prefsWinCon] ;
[prefsWinCon revealTabViewIdentifier1:constIdentifierTabViewGeneral
identifier2:nil] ;
}
}
// If OK, do it
if (alertReturn == NSAlertDefaultReturn) {
NSString* browserBundleIdentifier = nil ;
[SSWebBrowsing getActiveBrowserExtoreClass:NULL
exformat_p:NULL
bundleIdentifier_p:&browserBundleIdentifier
path_p:NULL] ;
for (Stark* bookmark in bookmarks) {
NSString* url = nil ;
switch (urlTemporality) {
case BkmxUrlTemporalityCurrent:
url = [bookmark url] ;
break ;
case BkmxUrlTemporalitySuggested:
url = [bookmark verifierSuggestedUrl] ;
break ;
case BkmxUrlTemporalityPrior:
url = [bookmark verifierPriorUrl] ;
}
if (url) {
if (!browserBundleIdentifier) {
// Active app is not a web browser. Use value from either Stark or document default
// Transform 'visitor' to the bundle identifier of the browser
// to use, as indicated by stark and BkmxDoc.shig settings
browserBundleIdentifier = [bookmark visitor] ;
if ([browserBundleIdentifier isEqualToString:constKeyVisitorDefaultDocument]) {
browserBundleIdentifier = [[(BkmxDoc*)[bookmark owner] shig] visitor] ;
}
if ([browserBundleIdentifier isEqualToString:constKeyVisitorDefaultBrowser]) {
browserBundleIdentifier = nil ; // Tells SSWebBrowsing to use default browser
}
}
NSUInteger cemf = [[[NSApplication sharedApplication] currentEvent] modifierFlags] ;
BOOL activateBrowser = ((cemf & NSAlternateKeyMask) != 0) == 0 ;
[SSWebBrowsing browseToURLString:url
browserBundleIdentifier:browserBundleIdentifier
activate:activateBrowser] ;
[bookmark setLastVisitedDate:[NSDate date]] ;
[bookmark setVisitCount:[NSNumber numberWithInteger:([[bookmark visitCount] integerValue] + 1)]] ;
}
}
}
}
[bookmarks release] ;
}
/*
I mistakenly removed this method in BookMacster 1.14.4, then restored it in
BookMacster 1.14.8. It is invoked when clicking a *bookmark* in one of the
hierarchical menus.
Starting with BookMacster 1.16, it is used by MiniSearchWinCon.
*/
+ (void)visitStark:(Stark*)stark {
[self visitItems:[NSArray arrayWithObject:stark]
urlTemporality:BkmxUrlTemporalityCurrent] ;
}
+ (void)swapPriorAndCurrentUrls:(id)sender {
NSArray* starks = [self starksFromSender:sender] ;
SSYDooDooUndoManager *undoer = (SSYDooDooUndoManager*)[[[starks lastObject] owner] undoManager] ;
for (Stark* stark in starks) {
NSString* priorToCurrent = [stark verifierPriorUrl] ;
NSString* currentToPrior = [stark url] ;
if (priorToCurrent && currentToPrior) {
[stark setVerifierPriorUrl:currentToPrior] ;
[stark setUrl:priorToCurrent] ;
}
}
NSString* actionName = [NSString stringWithFormat:
@"%@ (%@)",
[[BkmxBasis sharedBasis] labelSwapPriorAndCurrentUrl],
[starks readableName]] ;
[undoer setActionName:actionName] ;
}
+ (void)swapSuggestedAndCurrentUrls:(id)sender {
NSArray* starks = [self starksFromSender:sender] ;
SSYDooDooUndoManager *undoer = (SSYDooDooUndoManager*)[[[starks lastObject] owner] undoManager] ;
for (Stark* stark in starks) {
NSString* suggestedToCurrent = [stark verifierSuggestedUrl] ;
NSString* currentToSuggested = [stark url] ;
if (suggestedToCurrent && currentToSuggested) {
[stark setVerifierSuggestedUrl:currentToSuggested] ;
[stark setUrl:suggestedToCurrent] ;
}
}
NSString* actionName = [NSString stringWithFormat:
@"%@ (%@)",
[[BkmxBasis sharedBasis] labelSwapSuggestedAndCurrentUrl],
[starks readableName]] ;
[undoer setActionName:actionName] ;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment