Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
1536 lines (1367 sloc) 49.4 KB
//
// FoldersTree.m
// Vienna
//
// Created by Steve on Sat Jan 24 2004.
// Copyright (c) 2004-2005 Steve Palmer. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "FoldersTree.h"
#import "ImageAndTextCell.h"
#import "AppController.h"
#import "Constants.h"
#import "Preferences.h"
#import "HelperFunctions.h"
#import "StringExtensions.h"
#import "FolderView.h"
#import "PopupButton.h"
#import "ViennaApp.h"
#import "BrowserView.h"
#import "GoogleReader.h"
// Private functions
@interface FoldersTree (Private)
-(void)setFolderListFont;
-(NSArray *)archiveState;
-(void)unarchiveState:(NSArray *)stateArray;
-(void)reloadDatabase:(NSArray *)stateArray;
-(BOOL)loadTree:(NSArray *)listOfFolders rootNode:(TreeNode *)node;
-(void)setManualSortOrderForNode:(TreeNode *)node;
-(void)handleDoubleClick:(id)sender;
-(void)handleAutoSortFoldersTreeChange:(NSNotification *)nc;
-(void)handleFolderAdded:(NSNotification *)nc;
-(void)handleFolderNameChange:(NSNotification *)nc;
-(void)handleFolderUpdate:(NSNotification *)nc;
-(void)handleFolderDeleted:(NSNotification *)nc;
-(void)handleShowFolderImagesChange:(NSNotification *)nc;
-(void)handleFolderFontChange:(NSNotification *)nc;
-(void)reloadFolderItem:(id)node reloadChildren:(BOOL)flag;
-(void)expandToParent:(TreeNode *)node;
-(BOOL)copyTableSelection:(NSArray *)items toPasteboard:(NSPasteboard *)pboard;
-(BOOL)moveFolders:(NSArray *)array withGoogleSync:(BOOL)sync;
-(void)enableFoldersRenaming:(id)sender;
-(void)enableFoldersRenamingAfterDelay;
@end
@implementation FoldersTree
/* initWithFrame
* Initialise ourself.
*/
-(id)initWithFrame:(NSRect)frameRect
{
if ((self = [super initWithFrame:frameRect]) != nil)
{
// Root node is never displayed since we always display from
// the second level down. It simply provides a convenient way
// of containing the other nodes.
rootNode = [[TreeNode alloc] init:nil atIndex:0 folder:nil canHaveChildren:YES];
blockSelectionHandler = NO;
canRenameFolders = NO;
folderErrorImage = nil;
refreshProgressImage = nil;
operationQueue = [[NSOperationQueue alloc] init];
}
return self;
}
/* awakeFromNib
* Do things that only make sense once the NIB is loaded.
*/
-(void)awakeFromNib
{
NSTableColumn * tableColumn;
ImageAndTextCell * imageAndTextCell;
// Our folders have images next to them.
tableColumn = [outlineView tableColumnWithIdentifier:@"folderColumns"];
imageAndTextCell = [[[ImageAndTextCell alloc] init] autorelease];
[imageAndTextCell setEditable:YES];
[tableColumn setDataCell:imageAndTextCell];
// Folder image
folderErrorImage = [NSImage imageNamed:@"folderError.tiff"];
refreshProgressImage = [NSImage imageNamed:@"refreshProgress.tiff"];
// Create and set whatever font we're using for the folders
[self setFolderListFont];
// Set background colour
[outlineView setBackgroundColor:[NSColor colorWithCalibratedRed:0.84 green:0.87 blue:0.90 alpha:1.00]];
// Allow double-click a node to edit the node
[outlineView setAction:@selector(handleSingleClick:)];
[outlineView setDoubleAction:@selector(handleDoubleClick:)];
[outlineView setTarget:self];
// Initially size the outline view column to be the correct width
[outlineView sizeLastColumnToFit];
// Don't resize the column when items are expanded as this messes up
// the placement of the unread count button.
[outlineView setAutoresizesOutlineColumn:NO];
// Register for dragging
[outlineView registerForDraggedTypes:[NSArray arrayWithObjects:MA_PBoardType_FolderList, MA_PBoardType_RSSSource, @"WebURLsWithTitlesPboardType", NSStringPboardType, nil]];
[outlineView setVerticalMotionCanBeginDrag:YES];
// Make sure selected row is visible
[outlineView scrollRowToVisible:[outlineView selectedRow]];
}
/* setOutlineViewBackgroundColor
* Sets the color of the background view.
*/
-(void)setOutlineViewBackgroundColor: (NSColor *)color;
{
[outlineView setBackgroundColor: color];
}
/* initialiseFoldersTree
* Do the things to initialize the folder tree from the database
*/
-(void)initialiseFoldersTree
{
// Want tooltips
[outlineView setEnableTooltips:YES];
// Set the menu for the popup button
[outlineView setMenu:[[NSApp delegate] folderMenu]];
blockSelectionHandler = YES;
[self reloadDatabase:[[Preferences standardPreferences] arrayForKey:MAPref_FolderStates]];
blockSelectionHandler = NO;
// Register for notifications
NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:@selector(handleFolderUpdate:) name:@"MA_Notify_FoldersUpdated" object:nil];
[nc addObserver:self selector:@selector(handleFolderNameChange:) name:@"MA_Notify_FolderNameChanged" object:nil];
[nc addObserver:self selector:@selector(handleFolderAdded:) name:@"MA_Notify_FolderAdded" object:nil];
[nc addObserver:self selector:@selector(handleFolderDeleted:) name:@"MA_Notify_FolderDeleted" object:nil];
[nc addObserver:self selector:@selector(autoCollapseFolder:) name:@"MA_Notify_AutoCollapseFolder" object:nil];
[nc addObserver:self selector:@selector(handleFolderFontChange:) name:@"MA_Notify_FolderFontChange" object:nil];
[nc addObserver:self selector:@selector(handleShowFolderImagesChange:) name:@"MA_Notify_ShowFolderImages" object:nil];
[nc addObserver:self selector:@selector(handleAutoSortFoldersTreeChange:) name:@"MA_Notify_AutoSortFoldersTreeChange" object:nil];
[nc addObserver:self selector:@selector(handleGRSFolderChange:) name:@"MA_Notify_GRSFolderChange" object:nil];
}
-(void)handleGRSFolderChange:(NSNotification *)nc
{
// No need to sync with Google because this is triggered when Google Reader
// folder layout has changed. Making a sync call would be redundant.
[self moveFolders:[nc object] withGoogleSync:NO];
}
/* handleFolderFontChange
* Called when the user changes the folder font and/or size in the Preferences
*/
-(void)handleFolderFontChange:(NSNotification *)nc
{
[self setFolderListFont];
[outlineView reloadData];
}
/* setFolderListFont
* Creates or updates the fonts used by the article list. The folder
* list isn't automatically refreshed afterward - call reloadData for that.
*/
-(void)setFolderListFont
{
int height;
[cellFont release];
[boldCellFont release];
Preferences * prefs = [Preferences standardPreferences];
cellFont = [[NSFont fontWithName:[prefs folderListFont] size:[prefs folderListFontSize]] retain];
boldCellFont = [[[NSFontManager sharedFontManager] convertWeight:YES ofFont:cellFont] retain];
height = [[[NSApp delegate] layoutManager] defaultLineHeightForFont:boldCellFont];
[outlineView setRowHeight:height + 3];
[outlineView setIntercellSpacing:NSMakeSize(10, 4)];
}
/* reloadDatabase
* Refresh the folders tree and restore the specified archived state
*/
-(void)reloadDatabase:(NSArray *)stateArray
{
[rootNode removeChildren];
if (![self loadTree:[[Database sharedDatabase] arrayOfFolders:MA_Root_Folder] rootNode:rootNode])
{
[[Preferences standardPreferences] setFoldersTreeSortMethod:MA_FolderSort_ByName];
[rootNode removeChildren];
[self loadTree:[[Database sharedDatabase] arrayOfFolders:MA_Root_Folder] rootNode:rootNode];
}
[outlineView reloadData];
[self unarchiveState:stateArray];
}
/* saveFolderSettings
* Preserve the expanded/collapsed and selection state of the folders list
* into the user's preferences.
*/
-(void)saveFolderSettings
{
[[Preferences standardPreferences] setArray:[self archiveState] forKey:MAPref_FolderStates];
}
/* archiveState
* Creates an NSArray of states for every item in the tree that has a non-normal state.
*/
-(NSArray *)archiveState
{
NSMutableArray * archiveArray = [NSMutableArray arrayWithCapacity:16];
int count = [outlineView numberOfRows];
int index;
for (index = 0; index < count; ++index)
{
TreeNode * node = (TreeNode *)[outlineView itemAtRow:index];
BOOL isItemExpanded = [outlineView isItemExpanded:node];
BOOL isItemSelected = [outlineView isRowSelected:index];
if (isItemExpanded || isItemSelected)
{
NSDictionary * newDict = [[NSMutableDictionary alloc] init];
[newDict setValue:[NSNumber numberWithInt:[node nodeId]] forKey:@"NodeID"];
[newDict setValue:[NSNumber numberWithBool:isItemExpanded] forKey:@"ExpandedState"];
[newDict setValue:[NSNumber numberWithBool:isItemSelected] forKey:@"SelectedState"];
[archiveArray addObject:newDict];
[newDict release];
}
}
return archiveArray;
}
/* unarchiveState
* Unarchives an array of states.
* BUGBUG: Restoring multiple selections is not working.
*/
-(void)unarchiveState:(NSArray *)stateArray
{
for (NSDictionary * dict in stateArray)
{
int folderId = [[dict valueForKey:@"NodeID"] intValue];
TreeNode * node = [rootNode nodeFromID:folderId];
if (node != nil)
{
BOOL doExpandItem = [[dict valueForKey:@"ExpandedState"] boolValue];
BOOL doSelectItem = [[dict valueForKey:@"SelectedState"] boolValue];
if ([outlineView isExpandable:node] && doExpandItem)
[outlineView expandItem:node];
if (doSelectItem)
{
int row = [outlineView rowForItem:node];
if (row >= 0)
{
NSIndexSet * indexes = [NSIndexSet indexSetWithIndex:(NSUInteger )row];
[outlineView selectRowIndexes:indexes byExtendingSelection:YES];
}
}
}
}
[outlineView sizeToFit];
}
/* loadTree
* Recursive routine that populates the folder list
*/
-(BOOL)loadTree:(NSArray *)listOfFolders rootNode:(TreeNode *)node
{
Folder * folder;
if ([[Preferences standardPreferences] foldersTreeSortMethod] != MA_FolderSort_Manual)
{
for (folder in listOfFolders)
{
int itemId = [folder itemId];
NSArray * listOfSubFolders = [[Database sharedDatabase] arrayOfFolders:itemId];
int count = [listOfSubFolders count];
TreeNode * subNode;
subNode = [[TreeNode alloc] init:node atIndex:-1 folder:folder canHaveChildren:(count > 0)];
if (count)
[self loadTree:listOfSubFolders rootNode:subNode];
[subNode release];
}
}
else
{
NSArray * listOfFolderIds = [listOfFolders valueForKey:@"itemId"];
NSUInteger index = 0;
NSInteger nextChildId = (node == rootNode) ? [[Database sharedDatabase] firstFolderId] : [[node folder] firstChildId];
while (nextChildId > 0)
{
NSUInteger listIndex = [listOfFolderIds indexOfObject:[NSNumber numberWithInt:nextChildId]];
if (listIndex == NSNotFound)
{
NSLog(@"Cannot find child with id %ld for folder with id %ld", (long)nextChildId, (long)[node nodeId]);
return NO;
}
folder = [listOfFolders objectAtIndex:listIndex];
NSArray * listOfSubFolders = [[Database sharedDatabase] arrayOfFolders:nextChildId];
NSUInteger count = [listOfSubFolders count];
TreeNode * subNode;
subNode = [[TreeNode alloc] init:node atIndex:index folder:folder canHaveChildren:(count > 0)];
if (count)
{
if (![self loadTree:listOfSubFolders rootNode:subNode])
{
[subNode release];
return NO;
}
}
[subNode release];
nextChildId = [folder nextSiblingId];
++index;
}
if (index < [listOfFolders count])
{
NSLog(@"Missing children for folder with id %ld, %ld", (long)nextChildId, (long)[node nodeId]);
return NO;
}
}
return YES;
}
/* folders
* Returns an array that contains the all RSS folders in the database
* ordered by the order in which they appear in the folders list view.
*/
-(NSArray *)folders:(int)folderId
{
NSMutableArray * array = [NSMutableArray array];
TreeNode * node;
if (!folderId)
node = rootNode;
else
node = [rootNode nodeFromID:folderId];
if ([node folder] != nil && (IsRSSFolder([node folder]) || IsGoogleReaderFolder([node folder])))
[array addObject:[node folder]];
node = [node firstChild];
while (node != nil)
{
[array addObjectsFromArray:[self folders:[node nodeId]]];
node = [node nextSibling];
}
return array;
}
/* updateAlternateMenuTitle
* Sets the appropriate title for the alternate item in the contextual menu
* when user changes preferences for opening pages in external browser
*/
-(void)updateAlternateMenuTitle
{
NSMenuItem * mainMenuItem = menuItemWithAction(@selector(viewSourceHomePageInAlternateBrowser:));
if (mainMenuItem == nil)
return;
NSString * menuTitle = [mainMenuItem title];
int index;
NSMenu * folderMenu = [outlineView menu];
if (folderMenu != nil)
{
index = [folderMenu indexOfItemWithTarget:nil andAction:@selector(viewSourceHomePageInAlternateBrowser:)];
if (index >= 0)
{
NSMenuItem * contextualItem = [folderMenu itemAtIndex:index];
[contextualItem setTitle:menuTitle];
}
}
}
/* updateFolder
* Redraws a folder node and optionally recurses up and redraws all our
* parent nodes too.
*/
-(void)updateFolder:(int)folderId recurseToParents:(BOOL)recurseToParents
{
TreeNode * node = [rootNode nodeFromID:folderId];
if (node != nil)
{
[outlineView reloadItem:node reloadChildren:YES];
if (recurseToParents)
{
while ([node parentNode] != rootNode)
{
node = [node parentNode];
[outlineView reloadItem:node];
}
}
}
}
/* canDeleteFolderAtRow
* Returns YES if the folder at the specified row can be deleted, otherwise NO.
*/
-(BOOL)canDeleteFolderAtRow:(int)row
{
if (row >= 0)
{
TreeNode * node = [outlineView itemAtRow:row];
if (node != nil)
{
Folder * folder = [[Database sharedDatabase] folderFromID:[node nodeId]];
return folder && !IsSearchFolder(folder) && !IsTrashFolder(folder) && ![[Database sharedDatabase] readOnly] && [[outlineView window] isVisible];
}
}
return NO;
}
/* selectFolder
* Move the selection to the specified folder and make sure
* it's visible in the UI.
*/
-(BOOL)selectFolder:(int)folderId
{
TreeNode * node = [rootNode nodeFromID:folderId];
if (!node)
return NO;
// Walk up to our parent
[self expandToParent:node];
int rowIndex = [outlineView rowForItem:node];
if (rowIndex >= 0)
{
blockSelectionHandler = YES;
[outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:(NSUInteger )rowIndex] byExtendingSelection:NO];
[outlineView scrollRowToVisible:rowIndex];
[[NSNotificationCenter defaultCenter] postNotificationName:@"MA_Notify_FolderSelectionChange" object:node];
blockSelectionHandler = NO;
return YES;
}
return NO;
}
/* expandToParent
* Expands the parent nodes all the way up to the root to ensure
* that the node containing 'node' is visible.
*/
-(void)expandToParent:(TreeNode *)node
{
if ([node parentNode])
{
[self expandToParent:[node parentNode]];
[outlineView expandItem:[node parentNode]];
}
}
/* nextFolderWithUnreadAfterNode
* Finds the ID of the next folder after the specified node that has
* unread articles.
*/
-(int)nextFolderWithUnreadAfterNode:(TreeNode *)startingNode
{
TreeNode * node = startingNode;
while (node != nil)
{
TreeNode * nextNode = nil;
TreeNode * parentNode = [node parentNode];
if (([[node folder] childUnreadCount] > 0) && [outlineView isItemExpanded:node])
nextNode = [node firstChild];
if (nextNode == nil)
nextNode = [node nextSibling];
while (nextNode == nil && parentNode != nil)
{
nextNode = [parentNode nextSibling];
parentNode = [parentNode parentNode];
}
if (nextNode == nil)
nextNode = [rootNode firstChild];
if (([[nextNode folder] childUnreadCount]) && ![outlineView isItemExpanded:nextNode])
return [nextNode nodeId];
if ([[nextNode folder] unreadCount])
return [nextNode nodeId];
// If we've gone full circle and not found
// anything, we're out of unread articles
if (nextNode == startingNode)
return [startingNode nodeId];
node = nextNode;
}
return -1;
}
/* firstFolderWithUnread
* Finds the ID of the first folder that has unread articles.
*/
-(int)firstFolderWithUnread
{
// Get the first Node from the root node.
TreeNode * firstNode = [rootNode firstChild];
// Now get the ID of the next unread node after it and return it.
int nextNodeID = [self nextFolderWithUnreadAfterNode:firstNode];
return nextNodeID;
}
/* nextFolderWithUnread
* Finds the ID of the next folder after currentFolderId that has
* unread articles.
*/
-(int)nextFolderWithUnread:(int)currentFolderId
{
// Get the current Node from the ID.
TreeNode * currentNode = [rootNode nodeFromID:currentFolderId];
// Now get the ID of the next unread node after it and return it.
int nextNodeID = [self nextFolderWithUnreadAfterNode:currentNode];
return nextNodeID;
}
/* groupParentSelection
* If the selected folder is a group folder, it returns the ID of the group folder
* otherwise it returns the ID of the parent folder.
*/
-(int)groupParentSelection
{
Folder * folder = [[Database sharedDatabase] folderFromID:[self actualSelection]];
return folder ? ((IsGroupFolder(folder)) ? [folder itemId] : [folder parentId]) : MA_Root_Folder;
}
/* actualSelection
* Return the ID of the selected folder in the folder list.
*/
-(int)actualSelection
{
TreeNode * node = [outlineView itemAtRow:[outlineView selectedRow]];
return [node nodeId];
}
/* countOfSelectedFolders
* Return the total number of folders selected in the tree.
*/
-(int)countOfSelectedFolders
{
return [outlineView numberOfSelectedRows];
}
/* selectedFolders
* Returns an exclusive array of all selected folders. Exclusive means that if any folder is
* a group folder, we don't automatically return a list of all folders within that group.
*/
-(NSArray *)selectedFolders
{
NSIndexSet * rowIndexes = [outlineView selectedRowIndexes];
NSUInteger count = [rowIndexes count];
// Make a mutable array
NSMutableArray * arrayOfSelectedFolders = [NSMutableArray arrayWithCapacity:count];
if (count > 0)
{
NSUInteger index = [rowIndexes firstIndex];
while (index != NSNotFound)
{
TreeNode * node = [outlineView itemAtRow:index];
Folder * folder = [node folder];
if (folder != nil)
{
[arrayOfSelectedFolders addObject:folder];
}
index = [rowIndexes indexGreaterThanIndex:index];
}
}
return arrayOfSelectedFolders;
}
/* setManualSortOrderForNode
* Writes the order of the current folder hierarchy to the database.
*/
-(void)setManualSortOrderForNode:(TreeNode *)node
{
if (node == nil)
return;
Database * db = [Database sharedDatabase];
int folderId = [node nodeId];
int count = [node countOfChildren];
if (count > 0)
{
[db setFirstChild:[[node childByIndex:0] nodeId] forFolder:folderId];
[self setManualSortOrderForNode:[node childByIndex:0]];
int index;
for (index = 1; index < count; ++index)
{
[db setNextSibling:[[node childByIndex:index] nodeId] forFolder:[[node childByIndex:index - 1] nodeId]];
[self setManualSortOrderForNode:[node childByIndex:index]];
}
[db setNextSibling:0 forFolder:[[node childByIndex:index - 1] nodeId]];
}
else
[db setFirstChild:0 forFolder:folderId];
}
/* handleAutoSortFoldersTreeChange
* Respond to the notification when the preference is changed for automatically or manually sorting the folders tree.
*/
-(void)handleAutoSortFoldersTreeChange:(NSNotification *)nc
{
int selectedFolderId = [self actualSelection];
if ([[Preferences standardPreferences] foldersTreeSortMethod] == MA_FolderSort_Manual)
{
[[Database sharedDatabase] beginTransaction];
[self setManualSortOrderForNode:rootNode];
[[Database sharedDatabase] commitTransaction];
}
blockSelectionHandler = YES;
[self reloadDatabase:[[Preferences standardPreferences] arrayForKey:MAPref_FolderStates]];
blockSelectionHandler = NO;
// Make sure selected folder is visible
[self selectFolder:selectedFolderId];
}
/* handleShowFolderImagesChange
* Respond to the notification sent when the option to show folder images is changed.
*/
-(void)handleShowFolderImagesChange:(NSNotification *)nc
{
[outlineView reloadData];
}
/* handleSingleClick
* If the folder is already highlighted, then edit the folder name.
*/
-(void)handleSingleClick:(id)sender
{
if (canRenameFolders)
{
int clickedRow = [outlineView clickedRow];
if (clickedRow >= 0)
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(renameFolderByTimer:) userInfo:[outlineView itemAtRow:clickedRow] repeats:NO];
}
}
/* handleDoubleClick
* Handle the user double-clicking a node.
*/
-(void)handleDoubleClick:(id)sender
{
// Prevent the first click of the double click from triggering immediate folder name editing.
[self enableFoldersRenamingAfterDelay];
TreeNode * node = [outlineView itemAtRow:[outlineView selectedRow]];
if (IsRSSFolder([node folder]))
{
NSString * urlString = [[node folder] homePage];
if (urlString && ![urlString isBlank])
[[NSApp delegate] openURLFromString:urlString inPreferredBrowser:YES];
}
else if (IsSmartFolder([node folder]))
{
[[NSNotificationCenter defaultCenter] postNotificationName:@"MA_Notify_EditFolder" object:node];
}
}
/* handleFolderDeleted
* Called whenever a folder is removed from the database. We need
* to delete the associated tree nodes then select the next node, or
* the previous one if we were at the bottom of the list.
*/
-(void)handleFolderDeleted:(NSNotification *)nc
{
int currentFolderId = [controller currentFolderId];
int folderId = [(NSNumber *)[nc object] intValue];
TreeNode * thisNode = [rootNode nodeFromID:folderId];
TreeNode * nextNode;
// Stop any in process progress indicators.
[thisNode stopAndReleaseProgressIndicator];
// First find the next node we'll select
if ([thisNode nextSibling] != nil)
nextNode = [thisNode nextSibling];
else
{
nextNode = [thisNode parentNode];
if ([nextNode countOfChildren] > 1)
nextNode = [nextNode childByIndex:[nextNode countOfChildren] - 2];
}
// Ask our parent to delete us
TreeNode * ourParent = [thisNode parentNode];
[ourParent removeChild:thisNode andChildren:YES];
[self reloadFolderItem:ourParent reloadChildren:YES];
// Send the selection notification ourselves because if we're deleting at the end of
// the folder list, the selection won't actually change and outlineViewSelectionDidChange
// won't get tripped.
if (currentFolderId == folderId)
{
blockSelectionHandler = YES;
[self selectFolder:[nextNode nodeId]];
[[NSNotificationCenter defaultCenter] postNotificationName:@"MA_Notify_FolderSelectionChange" object:nextNode];
blockSelectionHandler = NO;
}
}
/* handleFolderNameChange
* Called whenever we need to redraw a specific folder, possibly because
* the unread count changed.
*/
-(void)handleFolderNameChange:(NSNotification *)nc
{
int folderId = [(NSNumber *)[nc object] intValue];
TreeNode * node = [rootNode nodeFromID:folderId];
TreeNode * parentNode = [node parentNode];
BOOL moveSelection = (folderId == [self actualSelection]);
if ([[Preferences standardPreferences] foldersTreeSortMethod] == MA_FolderSort_ByName)
[parentNode sortChildren:MA_FolderSort_ByName];
[self reloadFolderItem:parentNode reloadChildren:YES];
if (moveSelection)
{
int row = [outlineView rowForItem:node];
if (row >= 0)
{
blockSelectionHandler = YES;
NSIndexSet * indexes = [NSIndexSet indexSetWithIndex:(NSUInteger )row];
[outlineView selectRowIndexes:indexes byExtendingSelection:NO];
[outlineView scrollRowToVisible:row];
blockSelectionHandler = NO;
}
}
}
/* handleFolderUpdate
* Called whenever we need to redraw a specific folder, possibly because
* the unread count changed.
*/
-(void)handleFolderUpdate:(NSNotification *)nc
{
int folderId = [(NSNumber *)[nc object] intValue];
if (folderId == 0)
[self reloadFolderItem:rootNode reloadChildren:YES];
else
[self updateFolder:folderId recurseToParents:YES];
}
/* handleFolderAdded
* Called when a new folder is added to the database.
*/
-(void)handleFolderAdded:(NSNotification *)nc
{
Folder * newFolder = (Folder *)[nc object];
NSAssert(newFolder, @"Somehow got a NULL folder object here");
int parentId = [newFolder parentId];
TreeNode * node = (parentId == MA_Root_Folder) ? rootNode : [rootNode nodeFromID:parentId];
if (![node canHaveChildren])
[node setCanHaveChildren:YES];
int childIndex = -1;
if ([[Preferences standardPreferences] foldersTreeSortMethod] == MA_FolderSort_Manual)
{
int nextSiblingId = [newFolder nextSiblingId];
if (nextSiblingId > 0)
{
TreeNode * nextSibling = [node nodeFromID:nextSiblingId];
if (nextSibling != nil)
childIndex = [node indexOfChild:nextSibling];
}
}
TreeNode * newNode = [[TreeNode alloc] init:node atIndex:childIndex folder:newFolder canHaveChildren:NO];
[self reloadFolderItem:node reloadChildren:YES];
[newNode release];
}
/* reloadFolderItem
* Wrapper around reloadItem.
*/
-(void)reloadFolderItem:(id)node reloadChildren:(BOOL)flag
{
if (node == rootNode)
[outlineView reloadData];
else
[outlineView reloadItem:node reloadChildren:YES];
}
/* menuWillAppear
* Called when the popup menu is opened on the folder list. We move the
* selection to whichever node is under the cursor so the context between
* the menu items and the node is made clear.
*/
-(void)outlineView:(FolderView *)olv menuWillAppear:(NSEvent *)theEvent
{
int row = [olv rowAtPoint:[olv convertPoint:[theEvent locationInWindow] fromView:nil]];
if (row >= 0)
{
// Select the row under the cursor if it isn't already selected
if ([olv numberOfSelectedRows] <= 1)
[olv selectRowIndexes:[NSIndexSet indexSetWithIndex:(NSUInteger )row] byExtendingSelection:NO];
}
}
/* isItemExpandable
* Tell the outline view if the specified item can be expanded. The answer is
* yes if we have children, no otherwise.
*/
-(BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
TreeNode * node = (TreeNode *)item;
if (node == nil)
node = rootNode;
return [node canHaveChildren];
}
/* numberOfChildrenOfItem
* Returns the number of children belonging to the specified item
*/
-(int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
TreeNode * node = (TreeNode *)item;
if (node == nil)
node = rootNode;
return [node countOfChildren];
}
/* child
* Returns the child at the specified offset of the item
*/
-(id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
{
TreeNode * node = (TreeNode *)item;
if (node == nil)
node = rootNode;
return [node childByIndex:index];
}
/* tooltipForItem [dataSource]
* For items that have counts, we show a tooltip that aggregates the counts.
*/
-(NSString *)outlineView:(FolderView *)outlineView tooltipForItem:(id)item
{
TreeNode * node = (TreeNode *)item;
if (node != nil)
{
if ([[node folder] nonPersistedFlags] & MA_FFlag_Error)
return NSLocalizedString(@"An error occurred when this feed was last refreshed", nil);
if ([[node folder] childUnreadCount])
return [NSString stringWithFormat:NSLocalizedString(@"%d unread articles", nil), [[node folder] childUnreadCount]];
}
return nil;
}
/* objectValueForTableColumn
* Returns the actual string that is displayed in the cell. Folders that have child folders with unread
* articles show the aggregate unread article count.
*/
-(id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
TreeNode * node = (TreeNode *)item;
if (node == nil)
node = rootNode;
static NSDictionary * info = nil;
if (info == nil)
{
NSMutableParagraphStyle * style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[style setLineBreakMode:NSLineBreakByTruncatingMiddle];
info = [[NSDictionary alloc] initWithObjectsAndKeys:style, NSParagraphStyleAttributeName, nil];
[style release];
}
return [[[NSAttributedString alloc] initWithString:[node nodeName] attributes:info] autorelease];
}
/* willDisplayCell
* Hook before a cell is displayed to set the correct image for that cell. We use this to show the folder
* in normal or bold face depending on whether or not the folder (or sub-folders) have unread articles. This
* is also the place where we set the folder image.
*/
-(void)outlineView:(NSOutlineView *)olv willDisplayCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
if ([[tableColumn identifier] isEqualToString:@"folderColumns"])
{
TreeNode * node = (TreeNode *)item;
Folder * folder = [node folder];
ImageAndTextCell * realCell = (ImageAndTextCell *)cell;
// Set the colour of the text in the cell
int rowIndex = [olv rowForItem:item];
NSColor * textColor;
if (IsUnsubscribed(folder))
textColor = [NSColor grayColor];
else if ([olv editedRow] == rowIndex)
textColor = [NSColor blackColor];
else if ([olv selectedRow] == rowIndex)
textColor = [NSColor whiteColor];
else
textColor = [NSColor blackColor];
[realCell setTextColor:textColor];
[realCell setItem:item];
// Use the auxiliary position of the feed item to show
// the refresh icon if the feed is being refreshed, or an
// error icon if the feed failed to refresh last time.
if (IsUpdating(folder))
{
[realCell setAuxiliaryImage:nil];
[realCell setInProgress:YES];
}
else if (IsError(folder))
{
[realCell setAuxiliaryImage:folderErrorImage];
[realCell setInProgress:NO];
}
else
{
[realCell setAuxiliaryImage:nil];
[realCell setInProgress:NO];
}
if (IsSmartFolder(folder)) // Because if the search results contain unread articles we don't want the smart folder name to be bold.
{
[realCell clearCount];
[realCell setFont:cellFont];
}
else if ([folder unreadCount])
{
[realCell setFont:boldCellFont];
[realCell setCount:[folder unreadCount]];
[realCell setCountBackgroundColour:[NSColor colorForControlTint:[NSColor currentControlTint]]];
}
else if ([folder childUnreadCount] && ![olv isItemExpanded:item])
{
[realCell setFont:boldCellFont];
[realCell setCount:[folder childUnreadCount]];
[realCell setCountBackgroundColour:[NSColor colorForControlTint:[NSColor currentControlTint]]];
}
else
{
[realCell clearCount];
[realCell setFont:cellFont];
}
// Only show folder images if the user prefers them.
Preferences * prefs = [Preferences standardPreferences];
[realCell setImage:([prefs showFolderImages] ? [folder image] : [folder standardImage])];
}
}
/* mainView
* Return the main view of this class.
*/
-(NSView *)mainView
{
return outlineView;
}
/* outlineViewSelectionDidChange
* Called when the selection in the folder list has changed.
*/
-(void)outlineViewSelectionDidChange:(NSNotification *)notification
{
[self enableFoldersRenamingAfterDelay];
if (!blockSelectionHandler)
{
TreeNode * node = [outlineView itemAtRow:[outlineView selectedRow]];
[[NSNotificationCenter defaultCenter] postNotificationName:@"MA_Notify_FolderSelectionChange" object:node];
}
}
/* renameFolder
* Begin in-place editing of the selected folder name.
*/
-(void)renameFolder:(int)folderId
{
TreeNode * node = [rootNode nodeFromID:folderId];
NSInteger rowIndex = [outlineView rowForItem:node];
if (rowIndex != -1)
{
[outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:(NSUInteger )rowIndex] byExtendingSelection:NO];
[outlineView editColumn:[outlineView columnWithIdentifier:@"folderColumns"] row:rowIndex withEvent:nil select:YES];
}
}
/* renameFolderByTimer
* If no disabling events have occurred during the timer interval, rename the folder.
*/
-(void)renameFolderByTimer:(id)sender
{
if (canRenameFolders)
{
[self renameFolder:[(TreeNode *)[sender userInfo] nodeId]];
}
}
/* enableFoldersRenaming
* Enable the renaming of folders.
*/
-(void)enableFoldersRenaming:(id)sender
{
canRenameFolders = YES;
}
/* enableFoldersRenamingAfterDelay
* Set a timer to enable renaming of folders.
*/
-(void)enableFoldersRenamingAfterDelay
{
canRenameFolders = NO;
[NSTimer scheduledTimerWithTimeInterval:0.9 target:self selector:@selector(enableFoldersRenaming:) userInfo:nil repeats:NO];
}
/* outlineViewWillBecomeFirstResponder
* When outline view becomes first responder, bring the article view to the front,
* and prevent immediate folder renaming.
*/
-(void)outlineViewWillBecomeFirstResponder
{
[[controller browserView] setActiveTabToPrimaryTab];
[self enableFoldersRenamingAfterDelay];
}
/* shouldEditTableColumn [delegate]
* The editing of folder names will be handled by single clicks.
*/
-(BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
return NO;
}
/* setObjectValue [datasource]
* Update the folder name when the user has finished editing it.
*/
-(void)outlineView:(NSOutlineView *)olv setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
TreeNode * node = (TreeNode *)item;
NSString * newName = (NSString *)object;
Folder * folder = [node folder];
// Remove the "[G] " character on Google Reader feeds
if (IsGoogleReaderFolder(folder) && [newName hasPrefix:@"[G] "]) {
NSString *tmpName = [newName substringFromIndex:4];
newName = tmpName;
}
if (![[folder name] isEqualToString:newName])
{
Database * db = [Database sharedDatabase];
if ([db folderFromName:newName] != nil)
runOKAlertPanel(NSLocalizedString(@"Cannot rename folder", nil), NSLocalizedString(@"A folder with that name already exists", nil));
else
{
[db setFolderName:[folder itemId] newName:newName];
/*
GRSRenameFolderOperation * op = [[GRSRenameFolderOperation alloc] init];
[op setFolder:folder];
[op setOldName:oldName];
[op setNewName:newName];
[operationQueue addOperation:op];
[op release];
*/
}
}
}
/* validateDrop
* Called when something is being dragged over us. We respond with an NSDragOperation value indicating the
* feedback for the user given where we are.
*/
-(NSDragOperation)outlineView:(NSOutlineView*)olv validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
{
NSPasteboard * pb = [info draggingPasteboard];
NSString * type = [pb availableTypeFromArray:[NSArray arrayWithObjects:MA_PBoardType_FolderList, MA_PBoardType_RSSSource, @"WebURLsWithTitlesPboardType", NSStringPboardType, nil]];
NSDragOperation dragType = (type == MA_PBoardType_FolderList) ? NSDragOperationMove : NSDragOperationCopy;
TreeNode * node = (TreeNode *)item;
BOOL isOnDropTypeProposal = index == NSOutlineViewDropOnItemIndex;
// Can't drop anything onto the trash folder.
if (isOnDropTypeProposal && node != nil && IsTrashFolder([node folder]))
return NSDragOperationNone;
// Can't drop anything onto the search folder.
if (isOnDropTypeProposal && node != nil && IsSearchFolder([node folder]))
return NSDragOperationNone;
// Can't drop anything on smart folders.
if (isOnDropTypeProposal && node != nil && IsSmartFolder([node folder]))
return NSDragOperationNone;
// Can always drop something on a group folder.
if (isOnDropTypeProposal && node != nil && IsGroupFolder([node folder]))
return dragType;
// For any other folder, can't drop anything ON them.
if (index == NSOutlineViewDropOnItemIndex)
return NSDragOperationNone;
return NSDragOperationGeneric;
}
/* writeItems [delegate]
* Collect the selected folders ready for dragging.
*/
-(BOOL)outlineView:(NSOutlineView *)olv writeItems:(NSArray*)items toPasteboard:(NSPasteboard*)pboard
{
return [self copyTableSelection:items toPasteboard:pboard];
}
/* copyTableSelection
* This is the common copy selection code. We build an array of dictionary entries each of
* which include details of each selected folder in the standard RSS item format defined by
* Ranchero NetNewsWire. See http://ranchero.com/netnewswire/rssclipboard.php for more details.
*/
-(BOOL)copyTableSelection:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
{
int count = [items count];
NSMutableArray * externalDragData = [NSMutableArray arrayWithCapacity:count];
NSMutableArray * internalDragData = [NSMutableArray arrayWithCapacity:count];
NSMutableString * stringDragData = [NSMutableString string];
NSMutableArray * arrayOfURLs = [NSMutableArray arrayWithCapacity:count];
NSMutableArray * arrayOfTitles = [NSMutableArray arrayWithCapacity:count];
int index;
// We'll create the types of data on the clipboard.
[pboard declareTypes:[NSArray arrayWithObjects:MA_PBoardType_FolderList, MA_PBoardType_RSSSource, @"WebURLsWithTitlesPboardType", NSStringPboardType, nil] owner:self];
// Create an array of NSNumber objects containing the selected folder IDs.
int countOfItems = 0;
for (index = 0; index < count; ++index)
{
TreeNode * node = [items objectAtIndex:index];
Folder * folder = [node folder];
if (IsRSSFolder(folder) || IsGoogleReaderFolder(folder) || IsSmartFolder(folder) || IsGroupFolder(folder) || IsSearchFolder(folder) || IsTrashFolder(folder))
{
[internalDragData addObject:[NSNumber numberWithInt:[node nodeId]]];
++countOfItems;
}
if (IsRSSFolder(folder)||IsGoogleReaderFolder(folder))
{
NSString * feedURL = [folder feedURL];
NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];
[dict setValue:[folder name] forKey:@"sourceName"];
[dict setValue:[folder description] forKey:@"sourceDescription"];
[dict setValue:feedURL forKey:@"sourceRSSURL"];
[dict setValue:[folder homePage] forKey:@"sourceHomeURL"];
[externalDragData addObject:dict];
[dict release];
[stringDragData appendString:feedURL];
[stringDragData appendString:@"\n"];
NSURL * safariURL = [NSURL URLWithString:feedURL];
if (safariURL != nil && ![safariURL isFileURL])
{
if (![@"feed" isEqualToString:[safariURL scheme]])
{
feedURL = [NSString stringWithFormat:@"feed:%@", [safariURL resourceSpecifier]];
}
[arrayOfURLs addObject:feedURL];
[arrayOfTitles addObject:[folder name]];
}
}
}
// Copy the data to the pasteboard
[pboard setPropertyList:externalDragData forType:MA_PBoardType_RSSSource];
[pboard setString:stringDragData forType:NSStringPboardType];
[pboard setPropertyList:internalDragData forType:MA_PBoardType_FolderList];
[pboard setPropertyList:[NSArray arrayWithObjects:arrayOfURLs, arrayOfTitles, nil] forType:@"WebURLsWithTitlesPboardType"];
return countOfItems > 0;
}
/* moveFoldersUndo
* Undo handler to move folders back.
*/
-(void)moveFoldersUndo:(id)anObject
{
[self moveFolders:(NSArray *)anObject withGoogleSync:YES];
}
/* moveFolders
* Reparent folders using the information in the specified array. The array consists of
* a collection of NSNumber triples: the first number is the ID of the folder to move,
* the second number is the ID of the parent to which the folder should be moved,
* the third number is the ID of the folder's new predecessor sibling.
*/
-(BOOL)moveFolders:(NSArray *)array withGoogleSync:(BOOL)sync
{
NSAssert(([array count] % 3) == 0, @"Incorrect number of items in array passed to moveFolders");
int count = [array count];
int index = 0;
// Need to create a running undo array
NSMutableArray * undoArray = [[NSMutableArray alloc] initWithCapacity:count];
// Internal drag and drop so we're just changing the parent IDs around. One thing
// we have to watch for is to make sure that we don't re-parent to a subordinate
// folder.
Database * db = [Database sharedDatabase];
BOOL autoSort = [[Preferences standardPreferences] foldersTreeSortMethod] != MA_FolderSort_Manual;
[db beginTransaction];
while (index < count)
{
int folderId = [[array objectAtIndex:index++] intValue];
int newParentId = [[array objectAtIndex:index++] intValue];
int newPredecessorId = [[array objectAtIndex:index++] intValue];
Folder * folder = [db folderFromID:folderId];
int oldParentId = [folder parentId];
TreeNode * node = [rootNode nodeFromID:folderId];
TreeNode * oldParent = [rootNode nodeFromID:oldParentId];
int oldChildIndex = [oldParent indexOfChild:node];
int oldPredecessorId = (oldChildIndex > 0) ? [[oldParent childByIndex:(oldChildIndex - 1)] nodeId] : 0;
TreeNode * newParent = [rootNode nodeFromID:newParentId];
TreeNode * newPredecessor = [newParent nodeFromID:newPredecessorId];
if ((newPredecessor == nil) || (newPredecessor == newParent))
newPredecessorId = 0;
int newChildIndex = (newPredecessorId > 0) ? ([newParent indexOfChild:newPredecessor] + 1) : 0;
if (sync)
{
/*
GRSSetFolderOperation * op = [[GRSSetFolderOperation alloc] init];
[op setFolder:[node folder]];
[op setOldParent:[oldParent folder]];
[op setNewParent:[newParent folder]];
[operationQueue addOperation:op];
[op release];
*/
}
if (newParentId == oldParentId)
{
// With automatic sorting, moving under the same parent is impossible.
if (autoSort)
continue;
// No need to move if destination is the same as origin.
if (newPredecessorId == oldPredecessorId)
continue;
// Adjust the index for the removal of the old child.
if (newChildIndex > oldChildIndex)
--newChildIndex;
}
else
{
if (![newParent canHaveChildren])
[newParent setCanHaveChildren:YES];
if ([db setParent:newParentId forFolder:folderId])
{
if (IsGoogleReaderFolder(folder))
{
GoogleReader * myGoogle = [GoogleReader sharedManager];
NSString * folderName = [[db folderFromID:newParentId] name];
[myGoogle setFolder:folderName forFeed:[folder feedURL] folderFlag:TRUE];
}
}
else
continue;
}
if (!autoSort)
{
if (oldPredecessorId > 0)
{
if (![db setNextSibling:[folder nextSiblingId] forFolder:oldPredecessorId])
continue;
}
else
{
if (![db setFirstChild:[folder nextSiblingId] forFolder:oldParentId])
continue;
}
}
[node retain];
[oldParent removeChild:node andChildren:NO];
[newParent addChild:node atIndex:newChildIndex];
[node release];
// Put at beginning of undoArray in order to undo moves in reverse order.
[undoArray insertObject:[NSNumber numberWithInt:folderId] atIndex:0u];
[undoArray insertObject:[NSNumber numberWithInt:oldParentId] atIndex:1u];
[undoArray insertObject:[NSNumber numberWithInt:oldPredecessorId] atIndex:2u];
if (!autoSort)
{
if (newPredecessorId > 0)
{
if (![db setNextSibling:[[db folderFromID:newPredecessorId] nextSiblingId] forFolder:folderId])
continue;
[db setNextSibling:folderId forFolder:newPredecessorId];
}
else
{
int oldFirstChildId = (newParent == rootNode) ? [db firstFolderId] : [[newParent folder] firstChildId];
if (![db setNextSibling:oldFirstChildId forFolder:folderId])
continue;
[db setFirstChild:folderId forFolder:newParentId];
}
}
}
[db commitTransaction];
// If undo array is empty, then nothing has been moved.
if ([undoArray count] == 0u)
{
[undoArray release];
return NO;
}
// Set up to undo this action
NSUndoManager * undoManager = [[NSApp mainWindow] undoManager];
[undoManager registerUndoWithTarget:self selector:@selector(moveFoldersUndo:) object:undoArray];
[undoManager setActionName:NSLocalizedString(@"Move Folders", nil)];
[undoArray release];
// Make the outline control reload its data
[outlineView reloadData];
// If any parent was a collapsed group, expand it now
for (index = 0; index < count; index += 2)
{
int newParentId = [[array objectAtIndex:++index] intValue];
if (newParentId != MA_Root_Folder)
{
TreeNode * parentNode = [rootNode nodeFromID:newParentId];
if (![outlineView isItemExpanded:parentNode] && [outlineView isExpandable:parentNode])
[outlineView expandItem:parentNode];
}
}
// Properly set selection back to the original items. This has to be done after the
// refresh so that rowForItem returns the new positions.
NSMutableIndexSet * selIndexSet = [[NSMutableIndexSet alloc] init];
int selRowIndex = 9999;
for (index = 0; index < count; index += 2)
{
int folderId = [[array objectAtIndex:index++] intValue];
int rowIndex = [outlineView rowForItem:[rootNode nodeFromID:folderId]];
selRowIndex = MIN(selRowIndex, rowIndex);
[selIndexSet addIndex:rowIndex];
}
[outlineView scrollRowToVisible:selRowIndex];
[outlineView selectRowIndexes:selIndexSet byExtendingSelection:NO];
[selIndexSet release];
return YES;
}
/* acceptDrop
* Accept a drop on or between nodes either from within the folder view or from outside.
*/
-(BOOL)outlineView:(NSOutlineView *)olv acceptDrop:(id <NSDraggingInfo>)info item:(id)targetItem childIndex:(int)childIndex
{
NSPasteboard * pb = [info draggingPasteboard];
NSString * type = [pb availableTypeFromArray:[NSArray arrayWithObjects:MA_PBoardType_FolderList, MA_PBoardType_RSSSource, @"WebURLsWithTitlesPboardType", NSStringPboardType, nil]];
TreeNode * node = targetItem ? (TreeNode *)targetItem : rootNode;
int parentId = [node nodeId];
if ((childIndex == NSOutlineViewDropOnItemIndex) || (childIndex < 0))
childIndex = 0;
// Check the type
if (type == NSStringPboardType)
{
// This is possibly a URL that we'll handle as a potential feed subscription. It's
// not our call to make though.
int predecessorId = (childIndex > 0) ? [[node childByIndex:(childIndex - 1)] nodeId] : 0;
[[NSApp delegate] createNewSubscription:[pb stringForType:type] underFolder:parentId afterChild:predecessorId];
return YES;
}
if (type == MA_PBoardType_FolderList)
{
Database * db = [Database sharedDatabase];
NSArray * arrayOfSources = [pb propertyListForType:type];
int count = [arrayOfSources count];
int index;
int predecessorId = (childIndex > 0) ? [[node childByIndex:(childIndex - 1)] nodeId] : 0;
// Create an NSArray of triples (folderId, newParentId, predecessorId) that will be passed to moveFolders
// to do the actual move.
NSMutableArray * array = [[NSMutableArray alloc] initWithCapacity:count * 3];
int trashFolderId = [db trashFolderId];
for (index = 0; index < count; ++index)
{
int folderId = [[arrayOfSources objectAtIndex:index] intValue];
// Don't allow the trash folder to move under a group folder, because the group folder could get deleted.
// Also, don't allow perverse moves. We should probably do additional checking: not only whether the new parent
// is the folder itself but also whether the new parent is a subfolder.
if (((folderId == trashFolderId) && (node != rootNode)) || (folderId == parentId) || (folderId == predecessorId))
continue;
[array addObject:[NSNumber numberWithInt:folderId]];
[array addObject:[NSNumber numberWithInt:parentId]];
[array addObject:[NSNumber numberWithInt:predecessorId]];
predecessorId = folderId;
}
// Do the move
BOOL result = [self moveFolders:array withGoogleSync:YES];
[array release];
return result;
}
if (type == MA_PBoardType_RSSSource)
{
Database * db = [Database sharedDatabase];
NSArray * arrayOfSources = [pb propertyListForType:type];
int count = [arrayOfSources count];
int index;
// This is an RSS drag using the protocol defined by Ranchero for NetNewsWire. See
// http://ranchero.com/netnewswire/rssclipboard.php for more details.
//
int folderToSelect = -1;
for (index = 0; index < count; ++index)
{
[db beginTransaction];
NSDictionary * sourceItem = [arrayOfSources objectAtIndex:index];
NSString * feedTitle = [sourceItem valueForKey:@"sourceName"];
NSString * feedHomePage = [sourceItem valueForKey:@"sourceHomeURL"];
NSString * feedURL = [sourceItem valueForKey:@"sourceRSSURL"];
NSString * feedDescription = [sourceItem valueForKey:@"sourceDescription"];
if ((feedURL != nil) && [db folderFromFeedURL:feedURL] == nil)
{
int predecessorId = (childIndex > 0) ? [[node childByIndex:(childIndex - 1)] nodeId] : 0;
int folderId = [db addRSSFolder:feedTitle underParent:parentId afterChild:predecessorId subscriptionURL:feedURL];
if (feedDescription != nil)
[db setFolderDescription:folderId newDescription:feedDescription];
if (feedHomePage != nil)
[db setFolderHomePage:folderId newHomePage:feedHomePage];
if (folderId > 0)
folderToSelect = folderId;
++childIndex;
}
[db commitTransaction];
}
// If parent was a group, expand it now
if (parentId != MA_Root_Folder)
[outlineView expandItem:[rootNode nodeFromID:parentId]];
// Select a new folder
if (folderToSelect > 0)
[self selectFolder:folderToSelect];
return YES;
}
if (type == @"WebURLsWithTitlesPboardType")
{
Database * db = [Database sharedDatabase];
NSArray * webURLsWithTitles = [pb propertyListForType:type];
NSArray * arrayOfURLs = [webURLsWithTitles objectAtIndex:0];
NSArray * arrayOfTitles = [webURLsWithTitles objectAtIndex:1];
int count = [arrayOfURLs count];
int index;
int folderToSelect = -1;
for (index = 0; index < count; ++index)
{
[db beginTransaction];
NSString * feedTitle = [arrayOfTitles objectAtIndex:index];
NSString * feedURL = [arrayOfURLs objectAtIndex:index];
NSURL * draggedURL = [NSURL URLWithString:feedURL];
if (([draggedURL scheme] != nil) && [[draggedURL scheme] isEqualToString:@"feed"])
feedURL = [NSString stringWithFormat:@"http:%@", [draggedURL resourceSpecifier]];
if ([db folderFromFeedURL:feedURL] == nil)
{
int predecessorId = (childIndex > 0) ? [[node childByIndex:(childIndex - 1)] nodeId] : 0;
int newFolderId = [db addRSSFolder:feedTitle underParent:parentId afterChild:predecessorId subscriptionURL:feedURL];
if (newFolderId > 0)
folderToSelect = newFolderId;
++childIndex;
}
[db commitTransaction];
}
// If parent was a group, expand it now
if (parentId != MA_Root_Folder)
[outlineView expandItem:[rootNode nodeFromID:parentId]];
// Select a new folder
if (folderToSelect > 0)
[self selectFolder:folderToSelect];
return YES;
}
return NO;
}
/* dealloc
* Clean up and release resources.
*/
-(void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[cellFont release];
[boldCellFont release];
[folderErrorImage release];
[refreshProgressImage release];
[rootNode release];
[operationQueue release];
[super dealloc];
}
@end