FilteringArrayController.m [plain text]
/*
* Copyright (c) 2004-2007 Apple Inc. All rights reserved.
*
* IMPORTANT: This Apple software is supplied to you by Apple Inc. ("Apple") in
* consideration of your agreement to the following terms, and your use, installation,
* modification or redistribution of this Apple software constitutes acceptance of these
* terms. If you do not agree with these terms, please do not use, install, modify or
* redistribute this Apple software.
*
* In consideration of your agreement to abide by the following terms, and subject to these
* terms, Apple grants you a personal, non exclusive license, under AppleÕs copyrights in this
* original Apple software (the ÒApple SoftwareÓ), to use, reproduce, modify and redistribute
* the Apple Software, with or without modifications, in source and/or binary forms; provided
* that if you redistribute the Apple Software in its entirety and without modifications, you
* must retain this notice and the following text and disclaimers in all such redistributions
* of the Apple Software. Neither the name, trademarks, service marks or logos of Apple
* Computer, Inc. may be used to endorse or promote products derived from the Apple Software
* without specific prior written permission from Apple. Except as expressly stated in this
* notice, no other rights or licenses, express or implied, are granted by Apple herein,
* including but not limited to any patent rights that may be infringed by your derivative
* works or by other works in which the Apple Software may be incorporated.
*
* The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES,
* EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-
* INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE
* SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
*
* IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE,
* REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND
* WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
* OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
// Imports
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
#import "FilteringArrayController.h"
#import "SCSITargetProberKeys.h"
#import <Foundation/NSKeyValueObserving.h>
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
// Implementation
//ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
@implementation FilteringArrayController
// Called when the SCSITargetProberDocument nib loads.
- ( void ) awakeFromNib
{
// Initialize the search category to be by title (Description).
searchCategory = kSearchCategoryTitle;
// Set the placeholder string for the search category.
[ [ searchField cell ] setPlaceholderString: [ self placeholderForTag: searchCategory ] ];
// Setup the search menu.
[ self setupSearchMenu ];
// Observe any value changes to NSUserDefaultsController.
[ [ NSUserDefaultsController sharedUserDefaultsController ] addObserver: self
forKeyPath: kShowTargetIDKeyPath options: NSKeyValueObservingOptionNew context: nil ];
[ [ NSUserDefaultsController sharedUserDefaultsController ] addObserver: self
forKeyPath: kShowDescriptionKeyPath options: NSKeyValueObservingOptionNew context: nil ];
[ [ NSUserDefaultsController sharedUserDefaultsController ] addObserver: self
forKeyPath: kShowRevisionKeyPath options: NSKeyValueObservingOptionNew context: nil ];
[ [ NSUserDefaultsController sharedUserDefaultsController ] addObserver: self
forKeyPath: kShowFeaturesKeyPath options: NSKeyValueObservingOptionNew context: nil ];
[ [ NSUserDefaultsController sharedUserDefaultsController ] addObserver: self
forKeyPath: kShowPDTKeyPath options: NSKeyValueObservingOptionNew context: nil ];
}
// Called when values change for NSUserDefaultsController.
// See NSKeyValueObserving.h for more information.
- ( void ) observeValueForKeyPath: ( NSString * ) keyPath
ofObject: ( id ) object
change: ( NSDictionary * ) change
context: ( void * ) context
{
#pragma unused ( keyPath )
#pragma unused ( object )
#pragma unused ( change )
#pragma unused ( context )
// Set up the search menu based on any changes observed.
[ self setupSearchMenu ];
}
// Get a localized string to use as a placeholder in the NSSearchFieldCell
// based on the passed in tag (which corresponds to the selected menu item
// in the NSSearchFieldCell's menu).
- ( NSString * ) placeholderForTag: ( int ) tag
{
NSString * string = nil;
switch ( tag )
{
case kSearchCategoryID:
string = kIDString;
break;
case kSearchCategoryTitle:
string = kDescriptionString;
break;
case kSearchCategoryRevision:
string = kRevisionString;
break;
case kSearchCategoryFeatures:
string = kFeaturesString;
break;
case kSearchCategoryPDT:
string = kPDTString;
break;
}
// Get the localized string based on the string we have.
string = [ [ NSBundle mainBundle ] localizedStringForKey: string
value: string
table: nil ];
return string;
}
// Called to set up the search menu (removes items not
// checked in prefs box and sets checkmark on selected category).
- ( void ) setupSearchMenu
{
NSMenu * menu = nil;
id values = nil;
id item = nil;
int index = 0;
int count = 0;
BOOL itemSet = NO;
// Copy the menu in the nib.
menu = [ sortMenu copyWithZone: nil ];
values = [ [ NSUserDefaultsController sharedUserDefaultsController ] values ];
// Should we remove the "ID" category from the search menu?
if ( [ [ values valueForKey: kShowTargetIDString ] boolValue ] == NO )
{
// Yes, remove it.
item = [ menu itemWithTag: kSearchCategoryID ];
[ menu removeItem: item ];
}
// Should we remove the "Description" category from the search menu?
if ( [ [ values valueForKey: kShowDescriptionString ] boolValue ] == NO )
{
// Yes, remove it.
item = [ menu itemWithTag: kSearchCategoryTitle ];
[ menu removeItem: item ];
}
// Should we remove the "Revision" category from the search menu?
if ( [ [ values valueForKey: kShowRevisionString ] boolValue ] == NO )
{
// Yes, remove it.
item = [ menu itemWithTag: kSearchCategoryRevision ];
[ menu removeItem: item ];
}
// Should we remove the "Features" category from the search menu?
if ( [ [ values valueForKey: kShowFeaturesString ] boolValue ] == NO )
{
// Yes, remove it.
item = [ menu itemWithTag: kSearchCategoryFeatures ];
[ menu removeItem: item ];
}
// Should we remove the "PDT" category from the search menu?
if ( [ [ values valueForKey: kShowPDTString ] boolValue ] == NO )
{
// Yes, remove it.
item = [ menu itemWithTag: kSearchCategoryPDT ];
[ menu removeItem: item ];
}
count = [ menu numberOfItems ];
for ( index = 0; index < count; index++ )
{
item = [ menu itemAtIndex: index ];
// Is the item with this tag the one we're currently using as
// our search category?
if ( [ item tag ] != searchCategory )
{
// No, make sure checkmark is off.
[ item setState: NSOffState ];
}
else
{
// Yes, make sure checkmark is on.
[ item setState: NSOnState ];
// Make sure we mark that an item was set.
itemSet = YES;
}
}
// If there wasn't an item picked (because the preferences removed it), but there are
// still elements in the list, change the search category to the first available one.
if ( ( itemSet == NO ) && ( count > 0 ) )
{
[ [ searchField cell ] setStringValue: @"" ];
[ self changeSearchCategory: [ menu itemAtIndex: 0 ] ];
}
else
{
// Set the search menu template. If we didn't get into this else, then
// the search category will get changed and we will come back and hit
// this case for sure.
[ [ searchField cell ] setSearchMenuTemplate: menu ];
}
}
// Action called by NSSearchField when something has been typed in it.
- ( IBAction ) search: ( id ) sender
{
// Set the search string so we can use it in arrangeObjects:
searchString = [ sender stringValue ];
// Call rearrange objects. This decides to call arrangeObjects or not.
[ self rearrangeObjects ];
}
// Called by all menu items in the NSMenu attached to the NSSearchFieldCell
// to change the search category.
- ( IBAction ) changeSearchCategory: ( id ) sender
{
// Get the new category by the tag from the menu item which sent the
// action.
searchCategory = [ sender tag ];
// Make sure the UI updates the visible items based on the search
// category change.
[ self search: searchField ];
// Change the placeholder string so that when the NSSearchField is cleared
// or inactive, a placeholder exists.
[ [ searchField cell ] setPlaceholderString: [ self placeholderForTag: searchCategory ] ];
// Change the search menu (changes the checkmarks).
[ self setupSearchMenu ];
}
// Called to arrange objects.
- ( NSArray * ) arrangeObjects: ( NSArray * ) objects
{
// Is there a search string?
if ( ( searchString == nil ) || ( [ searchString isEqualToString: @"" ] ) )
{
// No, short circuit out...
return [ super arrangeObjects: objects ];
}
NSMutableArray * filteredObjects = nil;
NSEnumerator * objectsEnumerator = nil;
NSString * keyPath = nil;
id item = nil;
// Create an array of the same size as the original (so it's big
// enough to hold all the potential filtered objects)
filteredObjects = [ NSMutableArray arrayWithCapacity: [ objects count ] ];
// Figure out which key path to use based on the search category.
switch ( searchCategory )
{
case kSearchCategoryID:
keyPath = kDeviceIdentifierKeyPath;
break;
case kSearchCategoryTitle:
keyPath = kDeviceTitleKeyPath;
break;
case kSearchCategoryRevision:
keyPath = kDeviceRevisionKeyPath;
break;
case kSearchCategoryFeatures:
keyPath = kDeviceFeaturesKeyPath;
break;
case kSearchCategoryPDT:
keyPath = kDevicePDTKeyPath;
break;
}
// Get an object enumerator
objectsEnumerator = [ objects objectEnumerator ];
// Loop over the items and based on the search category, figure out if the
// item should remain visible in the table view or not.
item = [ objectsEnumerator nextObject ];
while ( item != nil )
{
NSString * lowerCaseSearchString = nil;
id value = nil;
// Change to lower case string for searching.
lowerCaseSearchString = [ searchString lowercaseString ];
// Get the value at the key path.
value = [ item valueForKeyPath: keyPath ];
// Add the object if it matches.
[ self addObject: item toArray: filteredObjects ifValue: value matchesString: lowerCaseSearchString ];
// Get the next object.
item = [ objectsEnumerator nextObject ];
}
return [ super arrangeObjects: filteredObjects ];
}
// Adds the object to the array if the matching string matches.
- ( void ) addObject: ( id ) object
toArray: ( NSMutableArray * ) array
ifValue: ( id ) inValue
matchesString: ( NSString * ) matchingString
{
// Is the value an NSArray of values?
if ( [ inValue isKindOfClass: [ NSArray class ] ] )
{
NSArray * values = nil;
int count = 0;
int index = 0;
// Yes, we have an NSArray of values. We have to loop over the whole array to
// determine if the item should be visible or not.
values = ( NSArray * ) inValue;
count = [ values count ];
for ( index = 0; index < count; index++ )
{
id value = nil;
// Get the object at index.
value = [ values objectAtIndex: index ];
// Call this method recursively (the object at that index could be an NSNumber,
// NSString, NSArray, etc.)
[ self addObject: object toArray: array ifValue: value matchesString: matchingString ];
}
}
// Is this value an NSNumber?
else if ( [ inValue isKindOfClass: [ NSNumber class ] ] )
{
NSString * string = nil;
string = [ [ inValue stringValue ] lowercaseString ];
// Is the search string anywhere to be found?
if ( [ string rangeOfString: matchingString ].location != NSNotFound )
{
// Yes, add the item to the filtered objects list.
[ array addObject: object ];
}
}
// Is this value an NSString?
else if ( [ inValue isKindOfClass: [ NSString class ] ] )
{
NSString * string = nil;
string = [ inValue lowercaseString ];
// Is the search string anywhere to be found?
if ( [ string rangeOfString: matchingString ].location != NSNotFound )
{
// Yes, add the item to the filtered objects list.
[ array addObject: object ];
}
}
// Add more class types as necessary...
}
@end