/* ** Toolbox.m ** ** Copyright (c) 2003 ** ** Author: Yen-Ju ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "Toolbox.h" #include "ExtendedBrowser.h" #include "ExtendedMatrix.h" #include "ItemPanel.h" #include "ItemImportPanel.h" #include "ItemInfoPanel.h" #include "ItemHelpPanel.h" #include "ToolboxWindow.h" #include #include #include @implementation Toolbox - (void) orderFrontItemInfoPanel: (id) sender { int selectedItem, selectedCategory = [browserView selectedRowInColumn: 0]; id object; if ([browserView selectedColumn] == 0) /* No item selected */ return; selectedItem = [browserView selectedRowInColumn: 1]; object = [[[dataSource objectAtIndex: selectedCategory] objectForKey: @"items"] objectAtIndex: selectedItem]; if ((selectedCategory > -1) || (selectedItem > -1)) { NSMutableDictionary *plist; NSString *infoPath, *iconPath; ItemInfoPanel *infoPanel; NSImage *icon; infoPath = [loader pathForResource: @"Info-gnustep" ofType: @"plist" ofBundle: [object objectForKey: @"bundle"]]; plist = [NSMutableDictionary dictionaryWithContentsOfFile: infoPath]; if([plist objectForKey: @"ApplicationName"] == nil) { [plist setObject: [object objectForKey: @"bundle"] forKey: @"ApplicationName"]; } if([plist objectForKey: @"ApplicationIcon"]) { iconPath = [[infoPath stringByDeletingLastPathComponent] stringByAppendingPathComponent: [plist objectForKey: @"ApplicationIcon"]]; icon = [[NSImage alloc] initWithContentsOfFile: iconPath]; /* iconPath expired because it share the same key of icon */ iconPath = nil; [plist setObject: icon forKey: @"ApplicationIcon"]; RELEASE(icon); } infoPanel = [[ItemInfoPanel alloc] initWithDictionary: plist]; [infoPanel setTitle: @"Toolbox Info Panel"]; [infoPanel setReleasedWhenClosed: YES]; [infoPanel orderFront: self]; } } - (void) orderFrontItemHelpPanel: (id) sender { NSString *bundle; if (sender == [itemPanel helpButton]) bundle = [[loader allNamesOfBundles] objectAtIndex: [itemBundleButton indexOfSelectedItem]]; else bundle = nil; if (helpPanel == nil) helpPanel = [[ItemHelpPanel alloc] initWithBundleName: bundle]; else [helpPanel setBundleName: bundle]; [helpPanel orderFront: self]; } - (void) reload: (id) sender { [currentBundle reload]; } - (void) addCategory: (id) sender { [dataSource addObject: [NSMutableDictionary dictionaryWithObjectsAndKeys: @"New category", @"name", [NSMutableArray new], @"items", nil]]; [browserView loadColumnZero]; [browserView selectRow: ([dataSource count]-1) inColumn: 0]; /* Start in-place editing */ [browserView doDoubleClick: [browserView matrixInColumn: 0]]; } - (void) deleteCategory: (id) sender { int index = [browserView selectedRowInColumn: 0]; if (index > -1) { int result = NSRunAlertPanel(_(@"Delete..."), _(@"Are you sure you want to delete this category?"), _(@"Cancel"), // default _(@"Yes"), // alternate NULL); if (result == NSAlertAlternateReturn) { [dataSource removeObjectAtIndex: index]; [browserView loadColumnZero]; } } } - (void) addItem: (id) sender { int i; NSArray *allBundles; Class bundleClass; allBundles = [loader allNamesOfBundles]; /* Refresh the categories */ if ([dataSource count] < 1) { NSRunAlertPanel(@"Warning: No category", @"You should add at least category firstly !", @"O.K.", nil, nil); return; } if ([allBundles count] < 1) { NSRunAlertPanel(@"Warning: No bundle", @"You should install at least bundle firstly !", @"O.K.", nil, nil); return; } [itemCategoryButton removeAllItems]; for(i = 0; i < [dataSource count]; i++) { [itemCategoryButton addItemWithTitle: [[dataSource objectAtIndex: i] objectForKey: @"name"]]; } [itemCategoryButton selectItemAtIndex: [browserView selectedRowInColumn: 0]]; [itemBundleButton removeAllItems]; for(i = 0; i < [allBundles count]; i++) { bundleClass = [loader classNameOf: [allBundles objectAtIndex: i]]; [itemBundleButton addItemWithTitle: [bundleClass bundleDescription]]; } [itemNameField setStringValue: @""]; [itemLocationField setStringValue: @""]; [itemPanel makeFirstResponder: itemNameField]; [NSApp runModalForWindow: itemPanel]; } - (void) deleteItem: (id) sender { int selectedCategory = [browserView selectedRowInColumn: 0]; int selectedItem = [browserView selectedRowInColumn: 1]; if ((selectedCategory > -1) && (selectedItem > -1)) { int result = NSRunAlertPanel(_(@"Delete..."), _(@"Are you sure you want to delete this tool?"), _(@"Cancel"), // default _(@"Yes"), // alternate NULL); if (result == NSAlertAlternateReturn) { [[[dataSource objectAtIndex: selectedCategory] objectForKey: @"items"] removeObjectAtIndex: selectedItem]; [browserView reloadColumn: 1]; [self browserAction: browserView]; } } } - (void) importItem: (id) sender { int i; NSArray *allBundles; Class bundleClass; allBundles = [loader allNamesOfBundles]; /* Refresh the categories */ if ([dataSource count] < 1) { NSRunAlertPanel(@"Warning: No category", @"You should add at least category firstly !", @"O.K.", nil, nil); return; } if ([allBundles count] < 1) { NSRunAlertPanel(@"Warning: No bundle", @"You should install at least bundle firstly !", @"O.K.", nil, nil); return; } [importCategoryButton removeAllItems]; for(i = 0; i < [dataSource count]; i++) { [importCategoryButton addItemWithTitle: [[dataSource objectAtIndex: i] objectForKey: @"name"]]; } [importCategoryButton selectItemAtIndex: [browserView selectedRowInColumn: 0]]; [importBundleButton removeAllItems]; for(i = 0; i < [allBundles count]; i++) { bundleClass = [loader classNameOf: [allBundles objectAtIndex: i]]; [importBundleButton addItemWithTitle: [bundleClass bundleDescription]]; } [importBundleButton addItemWithTitle: @"Local File"]; [importLocationField setStringValue: @""]; [importPanel makeFirstResponder: importLocationField]; /* Reset the locationField and itemButton */ [self importButtonAction: importBundleButton]; [NSApp runModalForWindow: importPanel]; } - (void) itemCancelAction: (id) sender { [NSApp abortModal]; } - (void) itemOKAction: (id) sender { NSMutableDictionary *dict = [NSMutableDictionary new]; NSArray *allBundles = [loader allNamesOfBundles]; int index = [itemCategoryButton indexOfSelectedItem]; [dict setObject: [itemNameField stringValue] forKey: @"name"]; [dict setObject: [itemLocationField stringValue] forKey: @"location"]; [dict setObject: [allBundles objectAtIndex: [itemBundleButton indexOfSelectedItem]] forKey: @"bundle"]; [[[dataSource objectAtIndex: index] objectForKey: @"items"] addObject: dict]; RELEASE(dict); [NSApp stopModal]; [itemPanel close]; [browserView selectRow: index inColumn: 0]; } - (void) itemButtonAction: (id) sender { } - (void) importOKAction: (id) sender { NSMutableDictionary *dict = [NSMutableDictionary new]; NSArray *allBundles = [loader allNamesOfBundles]; int selectedCategory = [importCategoryButton indexOfSelectedItem]; int selectedBundle = [importBundleButton indexOfSelectedItem]; id object; if ([importItemButton superview]) // using item button { int selectedItem = [importItemButton indexOfSelectedItem]; object = [defaultItems objectAtIndex: selectedItem]; [dict setObject: [object objectForKey: @"name"] forKey: @"name"]; [dict setObject: [object objectForKey: @"location"] forKey: @"location"]; [dict setObject: [allBundles objectAtIndex: selectedBundle] forKey: @"bundle"]; [[[dataSource objectAtIndex: selectedCategory] objectForKey: @"items"] addObject: dict]; } else // using location field { NSString *path = [[importLocationField stringValue] stringByStandardizingPath]; object = [NSArray arrayWithContentsOfFile: path]; if (object) { [[[dataSource objectAtIndex: selectedCategory] objectForKey: @"items"] addObjectsFromArray: object]; } } RELEASE(dict); [NSApp stopModal]; [importPanel close]; [browserView selectRow: selectedCategory inColumn: 0]; } - (void) importCancelAction: (id) sender { [NSApp abortModal]; } - (void) importButtonAction: (id) sender { NSString *path, *plistName; int index, i; id object; if (sender == importCategoryButton) return; [importDescriptionField setStringValue: @""]; if (sender == importBundleButton) { if ([sender indexOfSelectedItem] == [[loader allNamesOfBundles] count]) { if ([importLocationField superview]) /* Already in panel */ return; else /* Add importLocationField */ { RETAIN(importItemButton); [importItemButton removeFromSuperview]; [[importPanel contentView] addSubview: importLocationField]; RELEASE(importLocationField); } } else { if (![importItemButton superview]) /* Add importItemButton */ { RETAIN(importLocationField); [importLocationField removeFromSuperview]; [[importPanel contentView] addSubview: importItemButton]; RELEASE(importItemButton); } /* Set up the items */ index = [importBundleButton indexOfSelectedItem]; object = [[loader allNamesOfBundles] objectAtIndex: index]; plistName = [[object stringByDeletingPathExtension] stringByAppendingString: @"Defaults"]; path = [loader pathForResource: plistName ofType: @"plist" ofBundle: object]; [importItemButton removeAllItems]; if([[NSFileManager defaultManager] fileExistsAtPath: path]) { if ((object = [[NSString stringWithContentsOfFile: path] propertyList])) { for(i = 0; i < [object count]; i++) { [importItemButton addItemWithTitle: [[object objectAtIndex: i] objectForKey: @"name"]]; } [importItemButton setEnabled: YES]; [importDescriptionField setStringValue: [[object objectAtIndex: 0] objectForKey: @"description"]]; ASSIGN(defaultItems, object); return; } } [importItemButton addItemWithTitle: @"No Built-in Item"]; [importItemButton setEnabled: NO]; DESTROY(defaultItems); } } else if (sender == importItemButton) { /* Display the description */ if(defaultItems) { index = [importItemButton indexOfSelectedItem]; [importDescriptionField setStringValue: [[defaultItems objectAtIndex: index] objectForKey: @"description"]]; } } } /* Drag & Drop */ - (BOOL) matrix: (ExtendedMatrix *) view writeRows: (int) row inColumn: (int) column toPasteboard: (NSPasteboard *) pboard { NSData *data; int index; if (view == (ExtendedMatrix *)[browserView matrixInColumn: 0]) { data = [NSArchiver archivedDataWithRootObject: [dataSource objectAtIndex: row]]; } else { int index = [browserView selectedRowInColumn: 0]; data = [NSArchiver archivedDataWithRootObject: [[[dataSource objectAtIndex: index] objectForKey: @"items"] objectAtIndex: row]]; } [pboard declareTypes: [NSArray arrayWithObject: @"NSGeneralPboardType"] owner: nil]; [pboard setData: data forType: @"NSGeneralPboardType"]; return YES; } - (NSDragOperation) matrix: (ExtendedMatrix *) view validateDrop: (id ) info proposedRow: (int) row proposedDropOperation: (ExtendedMatrixDropOperation)operation { if ([info draggingSource] == [browserView matrixInColumn: 0]) { /* Category to Category */ if (view == (ExtendedMatrix *)[browserView matrixInColumn: 0]) [view setDropRow: row dropOperation: ExtendedMatrixDropAbove]; else /* Category to Item */ return NSDragOperationNone; } else /* from item */ { /* Item to Category */ if (view == (ExtendedMatrix *)[browserView matrixInColumn: 0]) [view setDropRow: row dropOperation: ExtendedMatrixDropOn]; else /* Item to Item */ [view setDropRow: row dropOperation: ExtendedMatrixDropAbove]; } return NSDragOperationMove; } - (BOOL) matrix: (ExtendedMatrix *) view acceptDrop: (id ) info row: (int) row proposedDropOperation: (ExtendedMatrixDropOperation)operation { NSPasteboard *pboard; NSData *data; NSMutableDictionary *dict; int selectedCategory = [browserView selectedRowInColumn: 0]; int selectedItem = [browserView selectedRowInColumn: 1]; id object; int index; pboard = [info draggingPasteboard]; data = [pboard dataForType: @"NSGeneralPboardType"]; dict = [NSUnarchiver unarchiveObjectWithData: data]; if (nil == [info draggingSource]) /* other application */ return NO; if (![[info draggingSource] isKindOfClass: [view class]]) return NO; if (operation == ExtendedMatrixDropAbove) { if ([dict objectForKey: @"bundle"]) /* item */ { object = [[dataSource objectAtIndex: selectedCategory] objectForKey: @"items"]; index = selectedItem; } else /* category */ { object = dataSource; index = selectedCategory; } if (index > row) /* move up (forward) */ { [object removeObjectAtIndex: index]; [object insertObject: dict atIndex: row]; } else if (index == row) { return YES; } else /* move down (backward) */ { [object insertObject: dict atIndex: row]; [object removeObjectAtIndex: index]; } // FIXME: Should only reload one column [browserView reloadColumn: 0]; [browserView selectRow: selectedCategory inColumn: 0]; [browserView reloadColumn: 1]; } else { if (selectedCategory == row) /* move to the same category */ return NO; [[[dataSource objectAtIndex: row] objectForKey: @"items"] addObject: dict]; [[[dataSource objectAtIndex: selectedCategory] objectForKey: @"items"] removeObjectAtIndex: selectedItem]; [browserView reloadColumn: 1]; } return YES; } /* Editing */ - (void) matrix: (NSMatrix *) matrix setObjectValue: (id) object forRow: (int) row column: (int) column { /* Don't use column because it must be 0 from NSBrowser */ if (matrix == [browserView matrixInColumn: 0]) { [[dataSource objectAtIndex: row] setObject: object forKey: @"name"]; } else if (matrix == [browserView matrixInColumn: 1]) { int index = [browserView selectedRowInColumn: 0]; if (index > -1) { NSMutableArray *array = [[dataSource objectAtIndex: index] objectForKey: @"items"]; [[array objectAtIndex: row] setObject: object forKey: @"name"]; } } } /* - (BOOL) browser: (NSBrowser *) browser selectRow: (int) row inColumn: (int) column { [browserView selectRow:row inColumn: column]; return YES; } */ - (void) browserAction: (id) sender { int selectedCategory = [sender selectedRowInColumn: 0]; int selectedItem = [sender selectedRowInColumn: 1]; NSRect browserFrame = [browserView frame]; NSRect windowFrame = [[window contentView] frame]; NSRect viewFrame; Class bundleClass; id object; [currentView removeFromSuperview]; RELEASE(currentBundle); currentView = nil; currentBundle = nil; if (([browserView selectedColumn] == 0) || (selectedItem < 0)) return; object = [[[dataSource objectAtIndex: selectedCategory] objectForKey: @"items"] objectAtIndex: selectedItem]; bundleClass = [loader classNameOf: [object objectForKey: @"bundle"]]; viewFrame = NSMakeRect(10, 10, browserFrame.size.width, browserFrame.origin.y - 10 - 10); currentBundle = [[bundleClass alloc] initWithFrame: viewFrame andLocation: [object objectForKey: @"location"]]; currentView = [currentBundle view]; if (currentView) { [currentView setFrame: viewFrame]; [[window contentView] addSubview: currentView]; [currentView setNeedsDisplay: YES]; [currentView displayIfNeeded]; } } - (NSString *) browser: (NSBrowser *) browser titleOfColumn: (int) column { if (column == 0) return @"Category"; else { if ([browser selectedRowInColumn: column-1] > -1) return [[browser selectedCellInColumn: column-1] stringValue]; else return nil; } } - (void) browser: (NSBrowser *) browser createRowsForColumn: (int) column inMatrix: (NSMatrix *) matrix { id cell; int i; if (column == 0) { if ([dataSource count] < 1) return; [matrix addColumn]; for (i = 0; i < [dataSource count]; i++) { if (i != 0) // First row is created when column is added [matrix insertRow: i]; cell = [matrix cellAtRow: i column: 0]; [cell setStringValue: [[dataSource objectAtIndex: i] objectForKey: @"name"]]; } /* set editorDelegate for browser */ [(ExtendedMatrix *) matrix setEditorDelegate: self]; [matrix registerForDraggedTypes: [NSArray arrayWithObject: NSGeneralPboardType]]; } else if (column == 1) { int index = [browserView selectedRowInColumn: 0]; if (index > -1) { NSMutableArray *array = [[dataSource objectAtIndex: index] objectForKey: @"items"]; if ([array count] < 1) return; [matrix addColumn]; for (i = 0; i < [array count]; i++) { if (i != 0) // First row is created when column is added [matrix insertRow: i]; cell = [matrix cellAtRow: i column: 0]; [cell setStringValue: [[array objectAtIndex: i] objectForKey: @"name"]]; [cell setLeaf: YES]; } /* set editorDelegate for browser */ [(ExtendedMatrix *) matrix setEditorDelegate: self]; [matrix registerForDraggedTypes: [NSArray arrayWithObject: NSGeneralPboardType]]; } } } - (void) applicationDidFinishLaunching: (NSNotification *) not { /* Create item panel */ itemPanel = [[ItemPanel alloc] initWithContentRect: NSMakeRect(300, 300, 420, 170) styleMask: NSTitledWindowMask backing: NSBackingStoreBuffered defer: YES]; [itemPanel layoutWindow]; [itemPanel makeFirstResponder: itemNameField]; [itemPanel setTitle: _(@"Add Item")]; itemCategoryButton = [itemPanel categoryButton]; itemNameField = [itemPanel nameField]; itemLocationField = [itemPanel locationField]; itemBundleButton = [itemPanel bundleButton]; [[itemPanel cancelButton] setTarget: self]; [[itemPanel cancelButton] setAction: @selector(itemCancelAction:)]; [[itemPanel okButton] setTarget: self]; [[itemPanel okButton] setAction: @selector(itemOKAction:)]; [[itemPanel helpButton] setTarget: self]; [[itemPanel helpButton] setAction: @selector(orderFrontItemHelpPanel:)]; [itemCategoryButton setTarget: self]; [itemCategoryButton setAction: @selector(itemButtonAction:)]; [itemBundleButton setTarget: self]; [itemBundleButton setAction: @selector(itemButtonAction:)]; /* Create item import panel */ importPanel = [[ItemImportPanel alloc] initWithContentRect: NSMakeRect(300, 300, 420, 170) styleMask: NSTitledWindowMask backing: NSBackingStoreBuffered defer: YES]; [importPanel layoutWindow]; [importPanel makeFirstResponder: importLocationField]; [importPanel setTitle: _(@"Import Item")]; importCategoryButton = [importPanel categoryButton]; importBundleButton = [importPanel bundleButton]; importLocationField = [importPanel locationField]; importItemButton = [importPanel itemButton]; importDescriptionField = [importPanel descriptionField]; [[importPanel cancelButton] setTarget: self]; [[importPanel cancelButton] setAction: @selector(importCancelAction:)]; [[importPanel okButton] setTarget: self]; [[importPanel okButton] setAction: @selector(importOKAction:)]; [[importPanel helpButton] setTarget: self]; [[importPanel helpButton] setAction: @selector(orderFrontItemHelpPanel:)]; [importCategoryButton setTarget: self]; [importCategoryButton setAction: @selector(importButtonAction:)]; [importBundleButton setTarget: self]; [importBundleButton setAction: @selector(importButtonAction:)]; [importItemButton setTarget: self]; [importItemButton setAction: @selector(importButtonAction:)]; [window makeKeyAndOrderFront: self]; [browserView loadColumnZero]; if ([dataSource count]) { [browserView selectRow: 0 inColumn: 0]; [self browserAction: browserView]; } } - (void) applicationWillFinishLaunching: (NSNotification *) not { NSMenu *menu; NSMenu *toolMenu; NSMenu *editMenu; NSMenu *servicesMenu; NSString *path; /* Set up data source */ path = [@"~/.Toolbox" stringByStandardizingPath]; if (![[NSFileManager defaultManager] fileExistsAtPath: path]) { NSBundle *b = [NSBundle mainBundle]; path = [b pathForResource: @"ToolboxDefaults" ofType: @"plist"]; } [dataSource setArray: [NSArray arrayWithContentsOfFile: path]]; /* Set up menu */ menu = [[NSMenu alloc] initWithTitle: _(@"Toolbox")]; [menu addItemWithTitle: _(@"Tool") action: NULL keyEquivalent: @""]; [menu addItemWithTitle: _(@"Edit") action: NULL keyEquivalent: @""]; [menu addItemWithTitle: _(@"Services") action: NULL keyEquivalent: @""]; [menu addItemWithTitle: _(@"Hide") action: @selector(hide:) keyEquivalent: @"h"]; [menu addItemWithTitle: _(@"Quit") action: @selector(terminate:) keyEquivalent: @"q"]; // Create the tool submenu toolMenu = [[NSMenu alloc] initWithTitle: _(@"Tool")]; [toolMenu addItemWithTitle: _(@"Tool Info...") action: @selector(orderFrontItemInfoPanel:) keyEquivalent: @"i"]; [toolMenu addItemWithTitle: _(@"Help") action: @selector(orderFrontItemHelpPanel:) keyEquivalent: @"?"]; [toolMenu addItemWithTitle: _(@"Reload") action: @selector(reload:) keyEquivalent: @"r"]; [menu setSubmenu: toolMenu forItem: [menu itemWithTitle: _(@"Tool")]]; RELEASE(toolMenu); // Create the edit submenu editMenu = [[NSMenu alloc] initWithTitle: _(@"Edit")]; [editMenu addItemWithTitle: _(@"Add Category") action: @selector(addCategory:) keyEquivalent: @"A"]; [editMenu addItemWithTitle: _(@"Delete Category") action: @selector(deleteCategory:) keyEquivalent: @"D"]; [editMenu addItemWithTitle: _(@"Add Item") action: @selector(addItem:) keyEquivalent: @"a"]; [editMenu addItemWithTitle: _(@"Delete Item") action: @selector(deleteItem:) keyEquivalent: @"d"]; [editMenu addItemWithTitle: _(@"Import Item") action: @selector(importItem:) keyEquivalent: @"x"]; [menu setSubmenu: editMenu forItem: [menu itemWithTitle: _(@"Edit")]]; RELEASE(editMenu); servicesMenu = [[NSMenu alloc] initWithTitle: _(@"Services")]; [menu setSubmenu: servicesMenu forItem: [menu itemWithTitle: _(@"Services")]]; [NSApp setServicesMenu: servicesMenu]; RELEASE(servicesMenu); [NSApp setMainMenu: menu]; RELEASE(menu); /* Set up toolbox window */ window = [[ToolboxWindow alloc] initWithContentRect: NSMakeRect(200, 200, 350, 270) styleMask: (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask) backing: NSBackingStoreBuffered defer: YES]; [window setDelegate: self]; [window layoutWindow]; [window setFrameAutosaveName: @"ToolboxWindow"]; [window setFrameUsingName: @"ToolboxWindow"]; [window setMinSize: NSMakeSize(350, 310)]; browserView = [window browser]; [browserView setMatrixClass: [ExtendedMatrix class]]; [browserView setDelegate: self]; [browserView setTarget: self]; [browserView setAction: @selector(browserAction:)]; [browserView setMaxVisibleColumns: 2]; [browserView setAllowsEmptySelection: NO]; [browserView setAllowsMultipleSelection: NO]; loader = [BundleLoader defaultLoader]; } - (BOOL) applicationShouldTerminateAfterLastWindowClosed: (id) sender { return YES; } - (void) applicationWillTerminate: (NSNotification *) not { NSString *path = [@"~/.Toolbox" stringByStandardizingPath]; [dataSource writeToFile: path atomically: YES]; } - (id) init { self = [super init]; dataSource = [NSMutableArray new]; return self; } - (void) dealloc { RELEASE(currentBundle); RELEASE(dataSource); RELEASE(defaultItems); [super dealloc]; } @end