1/* 2 * Copyright (c) 2004-2007 Apple Inc. All rights reserved. 3 * 4 * IMPORTANT: This Apple software is supplied to you by Apple Inc. ("Apple") in 5 * consideration of your agreement to the following terms, and your use, installation, 6 * modification or redistribution of this Apple software constitutes acceptance of these 7 * terms. If you do not agree with these terms, please do not use, install, modify or 8 * redistribute this Apple software. 9 * 10 * In consideration of your agreement to abide by the following terms, and subject to these 11 * terms, Apple grants you a personal, non exclusive license, under Apple�s copyrights in this 12 * original Apple software (the �Apple Software�), to use, reproduce, modify and redistribute 13 * the Apple Software, with or without modifications, in source and/or binary forms; provided 14 * that if you redistribute the Apple Software in its entirety and without modifications, you 15 * must retain this notice and the following text and disclaimers in all such redistributions 16 * of the Apple Software. Neither the name, trademarks, service marks or logos of Apple 17 * Computer, Inc. may be used to endorse or promote products derived from the Apple Software 18 * without specific prior written permission from Apple. Except as expressly stated in this 19 * notice, no other rights or licenses, express or implied, are granted by Apple herein, 20 * including but not limited to any patent rights that may be infringed by your derivative 21 * works or by other works in which the Apple Software may be incorporated. 22 * 23 * The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, 24 * EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON- 25 * INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE 26 * SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 27 * 28 * IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 30 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, 31 * REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND 32 * WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR 33 * OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 */ 35 36 37//����������������������������������������������������������������������������� 38// Imports 39//����������������������������������������������������������������������������� 40 41#import "FilteringArrayController.h" 42#import "SCSITargetProberKeys.h" 43#import <Foundation/NSKeyValueObserving.h> 44 45 46//����������������������������������������������������������������������������� 47// Implementation 48//����������������������������������������������������������������������������� 49 50@implementation FilteringArrayController 51 52 53// Called when the SCSITargetProberDocument nib loads. 54 55- ( void ) awakeFromNib 56{ 57 58 // Initialize the search category to be by title (Description). 59 searchCategory = kSearchCategoryTitle; 60 61 // Set the placeholder string for the search category. 62 [ [ searchField cell ] setPlaceholderString: [ self placeholderForTag: searchCategory ] ]; 63 64 // Setup the search menu. 65 [ self setupSearchMenu ]; 66 67 // Observe any value changes to NSUserDefaultsController. 68 [ [ NSUserDefaultsController sharedUserDefaultsController ] addObserver: self 69 forKeyPath: kShowTargetIDKeyPath options: NSKeyValueObservingOptionNew context: nil ]; 70 71 [ [ NSUserDefaultsController sharedUserDefaultsController ] addObserver: self 72 forKeyPath: kShowDescriptionKeyPath options: NSKeyValueObservingOptionNew context: nil ]; 73 74 [ [ NSUserDefaultsController sharedUserDefaultsController ] addObserver: self 75 forKeyPath: kShowRevisionKeyPath options: NSKeyValueObservingOptionNew context: nil ]; 76 77 [ [ NSUserDefaultsController sharedUserDefaultsController ] addObserver: self 78 forKeyPath: kShowFeaturesKeyPath options: NSKeyValueObservingOptionNew context: nil ]; 79 80 [ [ NSUserDefaultsController sharedUserDefaultsController ] addObserver: self 81 forKeyPath: kShowPDTKeyPath options: NSKeyValueObservingOptionNew context: nil ]; 82 83} 84 85 86// Called when values change for NSUserDefaultsController. 87// See NSKeyValueObserving.h for more information. 88 89- ( void ) observeValueForKeyPath: ( NSString * ) keyPath 90 ofObject: ( id ) object 91 change: ( NSDictionary * ) change 92 context: ( void * ) context 93{ 94 95#pragma unused ( keyPath ) 96#pragma unused ( object ) 97#pragma unused ( change ) 98#pragma unused ( context ) 99 100 // Set up the search menu based on any changes observed. 101 [ self setupSearchMenu ]; 102 103} 104 105 106// Get a localized string to use as a placeholder in the NSSearchFieldCell 107// based on the passed in tag (which corresponds to the selected menu item 108// in the NSSearchFieldCell's menu). 109 110- ( NSString * ) placeholderForTag: ( int ) tag 111{ 112 113 NSString * string = nil; 114 115 switch ( tag ) 116 { 117 118 case kSearchCategoryID: 119 string = kIDString; 120 break; 121 122 case kSearchCategoryTitle: 123 string = kDescriptionString; 124 break; 125 126 case kSearchCategoryRevision: 127 string = kRevisionString; 128 break; 129 130 case kSearchCategoryFeatures: 131 string = kFeaturesString; 132 break; 133 134 case kSearchCategoryPDT: 135 string = kPDTString; 136 break; 137 138 } 139 140 // Get the localized string based on the string we have. 141 string = [ [ NSBundle mainBundle ] localizedStringForKey: string 142 value: string 143 table: nil ]; 144 145 return string; 146 147} 148 149 150// Called to set up the search menu (removes items not 151// checked in prefs box and sets checkmark on selected category). 152 153- ( void ) setupSearchMenu 154{ 155 156 NSMenu * menu = nil; 157 id values = nil; 158 id item = nil; 159 int index = 0; 160 int count = 0; 161 BOOL itemSet = NO; 162 163 // Copy the menu in the nib. 164 menu = [ sortMenu copyWithZone: nil ]; 165 166 values = [ [ NSUserDefaultsController sharedUserDefaultsController ] values ]; 167 168 // Should we remove the "ID" category from the search menu? 169 if ( [ [ values valueForKey: kShowTargetIDString ] boolValue ] == NO ) 170 { 171 172 // Yes, remove it. 173 item = [ menu itemWithTag: kSearchCategoryID ]; 174 [ menu removeItem: item ]; 175 176 } 177 178 // Should we remove the "Description" category from the search menu? 179 if ( [ [ values valueForKey: kShowDescriptionString ] boolValue ] == NO ) 180 { 181 182 // Yes, remove it. 183 item = [ menu itemWithTag: kSearchCategoryTitle ]; 184 [ menu removeItem: item ]; 185 186 } 187 188 // Should we remove the "Revision" category from the search menu? 189 if ( [ [ values valueForKey: kShowRevisionString ] boolValue ] == NO ) 190 { 191 192 // Yes, remove it. 193 item = [ menu itemWithTag: kSearchCategoryRevision ]; 194 [ menu removeItem: item ]; 195 196 } 197 198 // Should we remove the "Features" category from the search menu? 199 if ( [ [ values valueForKey: kShowFeaturesString ] boolValue ] == NO ) 200 { 201 202 // Yes, remove it. 203 item = [ menu itemWithTag: kSearchCategoryFeatures ]; 204 [ menu removeItem: item ]; 205 206 } 207 208 // Should we remove the "PDT" category from the search menu? 209 if ( [ [ values valueForKey: kShowPDTString ] boolValue ] == NO ) 210 { 211 212 // Yes, remove it. 213 item = [ menu itemWithTag: kSearchCategoryPDT ]; 214 [ menu removeItem: item ]; 215 216 } 217 218 count = [ menu numberOfItems ]; 219 for ( index = 0; index < count; index++ ) 220 { 221 222 item = [ menu itemAtIndex: index ]; 223 224 // Is the item with this tag the one we're currently using as 225 // our search category? 226 if ( [ item tag ] != searchCategory ) 227 { 228 229 // No, make sure checkmark is off. 230 [ item setState: NSOffState ]; 231 232 } 233 234 else 235 { 236 237 // Yes, make sure checkmark is on. 238 [ item setState: NSOnState ]; 239 240 // Make sure we mark that an item was set. 241 itemSet = YES; 242 243 } 244 245 } 246 247 // If there wasn't an item picked (because the preferences removed it), but there are 248 // still elements in the list, change the search category to the first available one. 249 if ( ( itemSet == NO ) && ( count > 0 ) ) 250 { 251 [ [ searchField cell ] setStringValue: @"" ]; 252 [ self changeSearchCategory: [ menu itemAtIndex: 0 ] ]; 253 } 254 255 else 256 { 257 258 // Set the search menu template. If we didn't get into this else, then 259 // the search category will get changed and we will come back and hit 260 // this case for sure. 261 [ [ searchField cell ] setSearchMenuTemplate: menu ]; 262 263 } 264 265} 266 267 268// Action called by NSSearchField when something has been typed in it. 269 270- ( IBAction ) search: ( id ) sender 271{ 272 273 // Set the search string so we can use it in arrangeObjects: 274 searchString = [ sender stringValue ]; 275 276 // Call rearrange objects. This decides to call arrangeObjects or not. 277 [ self rearrangeObjects ]; 278 279} 280 281 282// Called by all menu items in the NSMenu attached to the NSSearchFieldCell 283// to change the search category. 284 285- ( IBAction ) changeSearchCategory: ( id ) sender 286{ 287 288 // Get the new category by the tag from the menu item which sent the 289 // action. 290 searchCategory = [ sender tag ]; 291 292 // Make sure the UI updates the visible items based on the search 293 // category change. 294 [ self search: searchField ]; 295 296 // Change the placeholder string so that when the NSSearchField is cleared 297 // or inactive, a placeholder exists. 298 [ [ searchField cell ] setPlaceholderString: [ self placeholderForTag: searchCategory ] ]; 299 300 // Change the search menu (changes the checkmarks). 301 [ self setupSearchMenu ]; 302 303} 304 305 306// Called to arrange objects. 307 308- ( NSArray * ) arrangeObjects: ( NSArray * ) objects 309{ 310 311 // Is there a search string? 312 if ( ( searchString == nil ) || ( [ searchString isEqualToString: @"" ] ) ) 313 { 314 // No, short circuit out... 315 return [ super arrangeObjects: objects ]; 316 } 317 318 NSMutableArray * filteredObjects = nil; 319 NSEnumerator * objectsEnumerator = nil; 320 NSString * keyPath = nil; 321 id item = nil; 322 323 // Create an array of the same size as the original (so it's big 324 // enough to hold all the potential filtered objects) 325 filteredObjects = [ NSMutableArray arrayWithCapacity: [ objects count ] ]; 326 327 // Figure out which key path to use based on the search category. 328 switch ( searchCategory ) 329 { 330 331 case kSearchCategoryID: 332 keyPath = kDeviceIdentifierKeyPath; 333 break; 334 335 case kSearchCategoryTitle: 336 keyPath = kDeviceTitleKeyPath; 337 break; 338 339 case kSearchCategoryRevision: 340 keyPath = kDeviceRevisionKeyPath; 341 break; 342 343 case kSearchCategoryFeatures: 344 keyPath = kDeviceFeaturesKeyPath; 345 break; 346 347 case kSearchCategoryPDT: 348 keyPath = kDevicePDTKeyPath; 349 break; 350 351 } 352 353 // Get an object enumerator 354 objectsEnumerator = [ objects objectEnumerator ]; 355 356 // Loop over the items and based on the search category, figure out if the 357 // item should remain visible in the table view or not. 358 item = [ objectsEnumerator nextObject ]; 359 while ( item != nil ) 360 { 361 362 NSString * lowerCaseSearchString = nil; 363 id value = nil; 364 365 // Change to lower case string for searching. 366 lowerCaseSearchString = [ searchString lowercaseString ]; 367 368 // Get the value at the key path. 369 value = [ item valueForKeyPath: keyPath ]; 370 371 // Add the object if it matches. 372 [ self addObject: item toArray: filteredObjects ifValue: value matchesString: lowerCaseSearchString ]; 373 374 // Get the next object. 375 item = [ objectsEnumerator nextObject ]; 376 377 } 378 379 return [ super arrangeObjects: filteredObjects ]; 380 381} 382 383 384// Adds the object to the array if the matching string matches. 385 386- ( void ) addObject: ( id ) object 387 toArray: ( NSMutableArray * ) array 388 ifValue: ( id ) inValue 389 matchesString: ( NSString * ) matchingString 390{ 391 392 // Is the value an NSArray of values? 393 if ( [ inValue isKindOfClass: [ NSArray class ] ] ) 394 { 395 396 NSArray * values = nil; 397 int count = 0; 398 int index = 0; 399 400 // Yes, we have an NSArray of values. We have to loop over the whole array to 401 // determine if the item should be visible or not. 402 403 values = ( NSArray * ) inValue; 404 count = [ values count ]; 405 406 for ( index = 0; index < count; index++ ) 407 { 408 409 id value = nil; 410 411 // Get the object at index. 412 value = [ values objectAtIndex: index ]; 413 414 // Call this method recursively (the object at that index could be an NSNumber, 415 // NSString, NSArray, etc.) 416 [ self addObject: object toArray: array ifValue: value matchesString: matchingString ]; 417 418 } 419 420 } 421 422 // Is this value an NSNumber? 423 else if ( [ inValue isKindOfClass: [ NSNumber class ] ] ) 424 { 425 426 NSString * string = nil; 427 428 string = [ [ inValue stringValue ] lowercaseString ]; 429 430 // Is the search string anywhere to be found? 431 if ( [ string rangeOfString: matchingString ].location != NSNotFound ) 432 { 433 434 // Yes, add the item to the filtered objects list. 435 [ array addObject: object ]; 436 437 } 438 439 } 440 441 // Is this value an NSString? 442 else if ( [ inValue isKindOfClass: [ NSString class ] ] ) 443 { 444 445 NSString * string = nil; 446 447 string = [ inValue lowercaseString ]; 448 449 // Is the search string anywhere to be found? 450 if ( [ string rangeOfString: matchingString ].location != NSNotFound ) 451 { 452 453 // Yes, add the item to the filtered objects list. 454 [ array addObject: object ]; 455 456 } 457 458 } 459 460 // Add more class types as necessary... 461 462} 463 464 465@end