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