Request info to get user icons when a Jabber contact signs on. Fixes #4205
[adiumx.git] / Source / ESStatusSort.m
blob61321fa6271ad2a0dbb16bf3e37e30ab6df0f54a
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 #import <Adium/AIContactControllerProtocol.h>
18 #import <Adium/AIPreferenceControllerProtocol.h>
19 #import "ESStatusSort.h"
20 #import <AIUtilities/AIDictionaryAdditions.h>
21 #import <Adium/AIListObject.h>
22 #import <Adium/AILocalizationTextField.h>
24 #define STATUS_SORT_DEFAULT_PREFS   @"StatusSortDefaults"
26 #define KEY_GROUP_AVAILABLE                     @"Status:Group Available"
27 #define KEY_GROUP_MOBILE                        @"Status:Group Mobile"
28 #define KEY_GROUP_UNAVAILABLE           @"Status:Group Unavailable"
29 #define KEY_GROUP_AWAY                          @"Status:Group Away"
30 #define KEY_GROUP_IDLE                          @"Status:Group Idle"
31 #define KEY_GROUP_IDLE_AND_AWAY         @"Status:Group Idle+Away"
32 #define KEY_SORT_IDLE_TIME                      @"Status:Sort by Idle Time"
33 #define KEY_RESOLVE_ALPHABETICALLY  @"Status:Resolve Alphabetically"
34 #define KEY_SORT_ORDER                          @"Status:Sort Order"
35 #define KEY_RESOLVE_BY_LAST_NAME        @"Status:Resolve Alphabetically By Last Name"
37 #define AVAILABLE                                       AILocalizedString(@"Available",nil)
38 #define AWAY                                            AILocalizedString(@"Away",nil)
39 #define IDLE                                            AILocalizedString(@"Idle",nil)
40 #define AWAY_AND_IDLE                           AILocalizedString(@"Away and Idle",nil)
41 #define UNAVAILABLE                                     AILocalizedString(@"Unavailable",nil)
42 #define OTHER_UNAVAILABLE                       AILocalizedString(@"Other Unavailable",nil)             
43 #define ONLINE                                          AILocalizedString(@"Online",nil)                
44 #define MOBILE                                          AILocalizedString(@"Mobile",nil)
46 #define STATUS_DRAG_TYPE                        @"Status Sort"
48 typedef enum {
49         Available = 0,
50         Away,
51         Idle,
52         Away_And_Idle,
53         Unavailable,
54         Online,
55         Mobile,
56         MAX_SORT_ORDER_DIMENSION
57 } Status_Sort_Type;
59 static BOOL groupAvailable;
60 static BOOL     groupMobile;
61 static BOOL groupUnavailable;
62 static BOOL     groupAway;
63 static BOOL     groupIdle;
64 static BOOL groupIdleAndAway;
65 static BOOL     sortIdleTime;
67 static BOOL     resolveAlphabetically;
68 static BOOL resolveAlphabeticallyByLastName;
70 static int  sortOrder[MAX_SORT_ORDER_DIMENSION];
71 static int  sizeOfSortOrder;
73 @interface ESStatusSort (PRIVATE)
74 - (void)configureControlDimming;
75 - (void)pruneAndSetSortOrderFromArray:(NSArray *)sortOrderArray;
76 @end
78 /*!
79  * @class ESStatusSort
80  * @brief AISortController to sort by contacts and groups
81  *
82  * Extensive configuration is allowed.
83  */
84 @implementation ESStatusSort
86 /*!
87  * @brief Did become active first time
88  *
89  * Called only once; gives the sort controller an opportunity to set defaults and load preferences lazily.
90  */
91 - (void)didBecomeActiveFirstTime
93         //Register our default preferences
94         [[adium preferenceController] registerDefaults:[NSDictionary dictionaryNamed:STATUS_SORT_DEFAULT_PREFS 
95                                                                                                                                                 forClass:[self class]] 
96                                                                                   forGroup:PREF_GROUP_CONTACT_SORTING];
97         
98         //Load our preferences
99         NSDictionary *prefDict = [[adium preferenceController] preferencesForGroup:PREF_GROUP_CONTACT_SORTING];
100         
101         groupAvailable = [[prefDict objectForKey:KEY_GROUP_AVAILABLE] boolValue];
102         groupMobile = [[prefDict objectForKey:KEY_GROUP_MOBILE] boolValue];
103         groupUnavailable = [[prefDict objectForKey:KEY_GROUP_UNAVAILABLE] boolValue];
104         
105         groupAway = [[prefDict objectForKey:KEY_GROUP_AWAY] boolValue];
106         groupIdle = [[prefDict objectForKey:KEY_GROUP_IDLE] boolValue];
107         groupIdleAndAway = [[prefDict objectForKey:KEY_GROUP_IDLE_AND_AWAY] boolValue];
108         
109         sortIdleTime = [[prefDict objectForKey:KEY_SORT_IDLE_TIME] boolValue];
110         resolveAlphabetically = [[prefDict objectForKey:KEY_RESOLVE_ALPHABETICALLY] boolValue];
111         resolveAlphabeticallyByLastName = [[prefDict objectForKey:KEY_RESOLVE_BY_LAST_NAME] boolValue];
112         
113         [self pruneAndSetSortOrderFromArray:[prefDict objectForKey:KEY_SORT_ORDER]];
117  * @brief Determines how the statusSort() method will operate.
119  * The sortOrder array, when it is done, contains, in order, the statuses which will be sorted upon.
121  * @param sortOrderArray An <tt>NSArray</tt> of <tt>NSNumber</tt>s whose values are Status_Sort_Type
122  */
123 - (void)pruneAndSetSortOrderFromArray:(NSArray *)sortOrderArray
125         NSEnumerator    *enumerator;
126         NSNumber                *sortTypeNumber;
127         int i;
128         
129         for (i = 0; i < MAX_SORT_ORDER_DIMENSION; i++) {
130                 sortOrder[i] = -1;
131         }
132         
133         i = 0;
134         
135         //Enumerate the ordering array.  For all sort types which are valid given the active sorting types,
136         //add to sortOrder[].  Finalize sortOrder with -1.
137         
138         BOOL    groupIdleOrIdleTime = (groupIdle || sortIdleTime);
140         enumerator = [sortOrderArray objectEnumerator];
141         while ((sortTypeNumber = [enumerator nextObject])) {
142                 switch ([sortTypeNumber intValue]) {
143                         case Available: 
144                                 /* Group available if:
145                                         Group available,
146                                         Group all unavailable, or 
147                                         Group separetely the idle and the away (such that the remaining alternative is Available)
148                                 */
149                                 if (groupAvailable || 
150                                         groupUnavailable ||
151                                         (/*!groupUnavailable &&*/ groupAway && groupIdleOrIdleTime)) sortOrder[i++] = Available;
152                                 break;
153                                 
154                         case Away:
155                                 if (!groupUnavailable && groupAway) sortOrder[i++] = Away;
156                                 break;
157                                 
158                         case Idle:
159                                 if ((!groupUnavailable && groupIdle) || sortIdleTime) sortOrder[i++] = Idle;
160                                 break;
161                                 
162                         case Away_And_Idle:
163                                 if (!groupUnavailable && groupIdleAndAway) sortOrder[i++] = Away_And_Idle;
164                                 break;
165                                 
166                         case Unavailable: 
167                                 //If one of groupAway or groupIdle is off, or we need a generic unavailable sort
168                                 if (groupUnavailable ||
169                                         ((groupAvailable && (!groupAway || !groupIdleOrIdleTime)))) {
170                                         sortOrder[i++] = Unavailable;
171                                 }
172                                 break;
173                                 
174                         case Online:
175                                 /* Show Online category if:
176                                         We aren't grouping all the available ones (this would imply grouping unavailable)
177                                         We aren't grouping all the unavailable ones (this would imply grouping available)
178                                         We aren't grouping both the away and the idle ones (this would imply grouping available)
179                                 */
180                                 if (!groupAvailable && !groupUnavailable && !(groupAway && (groupIdleOrIdleTime))) {
181                                         sortOrder[i++] = Online;
182                                 }
183                                 break;
184                                 
185                         case Mobile:
186                                 if (groupAvailable && groupMobile) {
187                                         sortOrder[i++] = Mobile;
188                                 }
189                                 break;
190                 }
191         }
192         
193         sortOrder[i] = -1;
194         
195         sizeOfSortOrder = i;
196         
197         [tableView_sortOrder reloadData];
201  * @brief Non-localized identifier
202  */
203 - (NSString *)identifier{
204     return @"by Status";
208  * @brief Localized display name
209  */
210 - (NSString *)displayName{
211     return AILocalizedString(@"Sort Contacts by Status",nil);
215  * @brief Status keys which, when changed, should trigger a resort
216  */
217 - (NSSet *)statusKeysRequiringResort{
218         return [NSSet setWithObjects:@"Online",@"Idle",@"StatusType",@"IsMobile",nil];
222  * @brief Attribute keys which, when changed, should trigger a resort
223  */
224 - (NSSet *)attributeKeysRequiringResort{
225         return [NSSet setWithObject:@"Display Name"];
229  * @brief Can the user manually reorder when this sort controller is active?
231  * The status sort can sort within status groupings either manually or alphabetically. Only the former should allow user reordering.
232  */
233 - (BOOL)canSortManually {
234         return !resolveAlphabetically;
237 //Configuration
238 #pragma mark Configuration
240  * @brief Window title when configuring the sort
242  * Subclasses should provide a title for configuring the sort only if configuration is possible.
243  * @result Localized title. If nil, the menu item will be disabled.
244  */
245 - (NSString *)configureSortWindowTitle{
246         return AILocalizedString(@"Configure Status Sort",nil); 
250  * @brief Nib name for configuration
251  */
252 - (NSString *)configureNibName{
253         return @"StatusSortConfiguration";
257  * @brief View did load
258  */
259 - (void)viewDidLoad
261         [checkBox_groupAvailable setState:groupAvailable];
262         [checkBox_groupMobileSeparately setState:groupMobile];
263         [checkBox_groupAway setState:groupAway];
264         [checkBox_groupIdle setState:groupIdle];
265         [checkBox_groupIdleAndAway setState:groupIdleAndAway];
266         [checkBox_sortIdleTime setState:sortIdleTime];
267         [checkBox_alphabeticallyByLastName setState:resolveAlphabeticallyByLastName];
268         
269         [buttonCell_alphabetically setState:(resolveAlphabetically ? NSOnState : NSOffState)];
270         [buttonCell_manually setState:(resolveAlphabetically ? NSOffState : NSOnState)];
272         [buttonCell_allUnavailable setState:(groupUnavailable ? NSOnState : NSOffState)];
273         [buttonCell_separateUnavailable setState:(groupUnavailable ? NSOffState : NSOnState)];
274         
275         [self configureControlDimming];
276         
277         [tableView_sortOrder setDataSource:self];
278         [tableView_sortOrder setDelegate:self];
279     [tableView_sortOrder registerForDraggedTypes:[NSArray arrayWithObject:STATUS_DRAG_TYPE]];
283  * @brief Preference changed
285  * Sort controllers should live update as preferences change.
286  */
287 - (IBAction)changePreference:(id)sender
289         NSArray *sortOrderArray =  [[adium preferenceController] preferenceForKey:KEY_SORT_ORDER
290                                                                                                                                                 group:PREF_GROUP_CONTACT_SORTING];
291         if (sender == checkBox_groupAvailable) {
292                 groupAvailable = [sender state];
293                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:groupAvailable]
294                                              forKey:KEY_GROUP_AVAILABLE
295                                               group:PREF_GROUP_CONTACT_SORTING];
297                 [self configureControlDimming];
298                 
299         } else if (sender == checkBox_groupMobileSeparately) {
300                 groupMobile = [sender state];
301                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:groupMobile]
302                                              forKey:KEY_GROUP_MOBILE
303                                               group:PREF_GROUP_CONTACT_SORTING];                
304                 
305                 //Ensure the mobile item is in our sort order array, as the old defaults didn't include it
306                 if ([sortOrderArray indexOfObject:[NSNumber numberWithInt:Mobile]] == NSNotFound) {
307                         NSMutableArray  *newSortOrderArray = [[sortOrderArray mutableCopy] autorelease];
308                         [newSortOrderArray addObject:[NSNumber numberWithInt:Mobile]];
309                         
310                         [[adium preferenceController] setPreference:newSortOrderArray
311                                                                                                  forKey:KEY_SORT_ORDER
312                                                                                                   group:PREF_GROUP_CONTACT_SORTING];
313                         
314                         sortOrderArray = newSortOrderArray;
315                 }
316                 
317         } else if (sender == checkBox_groupAway) {
318                 groupAway = [sender state];
319                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:groupAway]
320                                              forKey:KEY_GROUP_AWAY
321                                               group:PREF_GROUP_CONTACT_SORTING];                
322         } else if (sender == checkBox_groupIdle) {
323                 groupIdle = [sender state];
324                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:groupIdle]
325                                              forKey:KEY_GROUP_IDLE
326                                               group:PREF_GROUP_CONTACT_SORTING];
327                 
328         } else if (sender == checkBox_groupIdleAndAway) {
329                 groupIdleAndAway = [sender state];
330                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:groupIdleAndAway]
331                                              forKey:KEY_GROUP_IDLE_AND_AWAY
332                                               group:PREF_GROUP_CONTACT_SORTING];
333                 
334         } else if (sender == checkBox_sortIdleTime) {
335                 sortIdleTime = [sender state];
336                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:sortIdleTime]
337                                              forKey:KEY_SORT_IDLE_TIME
338                                               group:PREF_GROUP_CONTACT_SORTING];                                
339         } else if (sender == matrix_resolution) {
340                 id selectedCell = [sender selectedCell];
341                 
342                 resolveAlphabetically = (selectedCell == buttonCell_alphabetically);
343                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:resolveAlphabetically]
344                                                                                          forKey:KEY_RESOLVE_ALPHABETICALLY
345                                                                                           group:PREF_GROUP_CONTACT_SORTING];
346                 
347                 [self configureControlDimming];
348                 
349         } else if (sender == matrix_unavailableGrouping) {
350                 id selectedCell = [sender selectedCell];
351                 
352                 groupUnavailable = (selectedCell == buttonCell_allUnavailable);
353                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:groupUnavailable]
354                                                                                          forKey:KEY_GROUP_UNAVAILABLE
355                                                                                           group:PREF_GROUP_CONTACT_SORTING];
356                 
357                 [self configureControlDimming];
358                 
359         } else if (sender == checkBox_alphabeticallyByLastName) {
360                 resolveAlphabeticallyByLastName = [sender state];
361                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:resolveAlphabeticallyByLastName]
362                                              forKey:KEY_RESOLVE_BY_LAST_NAME
363                                               group:PREF_GROUP_CONTACT_SORTING];
364         }
365         
366         [self pruneAndSetSortOrderFromArray:sortOrderArray];
367         
368         [[adium contactController] sortContactList];
372  * @brief Configure control dimming
373  */
374 - (void)configureControlDimming
376         [checkBox_alphabeticallyByLastName setEnabled:resolveAlphabetically];
377         [checkBox_groupAway setEnabled:!groupUnavailable];
378         [checkBox_groupIdle setEnabled:!groupUnavailable];
379         [checkBox_groupIdleAndAway setEnabled:!groupUnavailable];
380         
381         [checkBox_groupMobileSeparately setEnabled:groupAvailable];
384 #pragma mark Sort Order Tableview datasource
386  * @brief Table view number of rows
387  */
388 - (int)numberOfRowsInTableView:(NSTableView *)tableView
390         return sizeOfSortOrder;
394  * @brief Table view object value
395  */
396 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
398         switch (sortOrder[rowIndex]) {
399                 case Available:
400                         return AVAILABLE;
401                         break;
402                         
403                 case Away:
404                         return AWAY;
405                         break;
406                         
407                 case Idle:
408                         return IDLE;
409                         break;
410                         
411                 case Away_And_Idle:
412                         return AWAY_AND_IDLE;
413                         break;
414                         
415                 case Unavailable:
416                         //Unavailable is always the same sort, but to the user it can be either "Unavailable" or "Other Unavailable"
417                         //depending upon what other options are active.  The test here is purely cosmetic.
418                         return ((!sortIdleTime && (groupUnavailable || !(groupAway || groupIdle || groupIdleAndAway))) ?
419                                         UNAVAILABLE :
420                                         OTHER_UNAVAILABLE);
421                         break;
422                 
423                 case Online:
424                         return ONLINE;
425                         break;
426                         
427                 case Mobile:
428                         return MOBILE;
429                         break;
430         }
431         
432         return @"";
436  * @brief The NSNumber Status_Sort_Type which corresponds to a string
438  * @param string A string such as AVAILABLE or AWAY (localized)
439  * @result The NSNumber Status_Sort_Type which corresponds to the string 
440  */
441 - (NSNumber *)numberForString:(NSString *)string
443         int equivalent = -1;
445         if ([string isEqualToString:AVAILABLE]) {
446                 equivalent = Available;
447         } else if ([string isEqualToString:AWAY]) {
448                 equivalent = Away;
449         } else if ([string isEqualToString:IDLE]) {
450                 equivalent = Idle;
451         } else if ([string isEqualToString:AWAY_AND_IDLE]) {
452                 equivalent = Away_And_Idle;
453         } else if ([string isEqualToString:UNAVAILABLE] || ([string isEqualToString:OTHER_UNAVAILABLE])) {
454                 equivalent = Unavailable;
455         } else if ([string isEqualToString:ONLINE]) {
456                 equivalent = Online;
457         } else if ([string isEqualToString:MOBILE]) {
458                 equivalent = Mobile;
459         }
460         
461         return [NSNumber numberWithInt:equivalent];
465  * @brief Table view write rows
466  */
467 -  (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray *)rows toPasteboard:(NSPasteboard *)pboard
469     [pboard declareTypes:[NSArray arrayWithObject:STATUS_DRAG_TYPE] owner:self];
470         
471     //Build a list of all the highlighted aways
472     NSString    *dragItem = [self tableView:tableView
473                                   objectValueForTableColumn:nil
474                                                                                 row:[[rows objectAtIndex:0] intValue]];
475         
476     //put it on the pasteboard
477     [pboard setString:dragItem forType:STATUS_DRAG_TYPE];
478         
479     return YES;
483  * @brief Table view validate drop
484  */
485 - (NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
487     NSString    *avaliableType = [[info draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObject:STATUS_DRAG_TYPE]];
489         if ([avaliableType isEqualToString:STATUS_DRAG_TYPE]) {
490         if (operation == NSTableViewDropAbove && row != -1) {
491             return NSDragOperationMove;
492         } else {
493             return NSDragOperationNone;
494                 }
495         }
496         
497     return NSDragOperationNone;
501  * @brief Table view accept drop
502  */
503 - (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
505     NSString            *availableType = [[info draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObject:STATUS_DRAG_TYPE]];
507     if ([availableType isEqualToString:STATUS_DRAG_TYPE]) {
508                 NSString                *item = [[info draggingPasteboard] stringForType:STATUS_DRAG_TYPE];
509                 
510                 //Remember, sortOrderPref contains all possible sorting types, not just the ones presently visible in the table!
511                 NSMutableArray  *sortOrderPref = [[[adium preferenceController] preferenceForKey:KEY_SORT_ORDER
512                                                                                                                                                                    group:PREF_GROUP_CONTACT_SORTING] mutableCopy];
513                 NSNumber                *sortNumber = [self numberForString:item];
514                 
515                 //Remove it from our array
516                 [sortOrderPref removeObject:sortNumber];
517                 
518                 if (row == [tableView numberOfRows]) {
519                         //Dropped at the bottom
520                         [sortOrderPref addObject:sortNumber];
521                 } else {
522                         //Find the object which will end up just below it
523                         int targetIndex = [sortOrderPref indexOfObject:[self numberForString:[self tableView:tableView
524                                                                                                                                                  objectValueForTableColumn:nil
525                                                                                                                                                                                            row:row]]];
526                         if (targetIndex != NSNotFound) {
527                                 //Insert it there
528                                 [sortOrderPref insertObject:sortNumber atIndex:targetIndex];
529                         } else {
530                                 //Dropped at the bottom
531                                 [sortOrderPref addObject:sortNumber];
532                         }
533                 }
534                 
535                 [[adium preferenceController] setPreference:sortOrderPref
536                                                                                          forKey:KEY_SORT_ORDER
537                                                                                           group:PREF_GROUP_CONTACT_SORTING];
538                 
539                 [self pruneAndSetSortOrderFromArray:sortOrderPref];             
540                 
541                 //Select and scroll to the dragged object
542                 [tableView reloadData];
543                 
544                 [[adium contactController] sortContactList];
546                 [sortOrderPref release];
547         }
548         
549         
550     return YES;
554 #pragma mark Sorting
557  * @brief The status sort method itself
559  * It's magic... but it's efficient magic!
560  */
561 int statusSort(id objectA, id objectB, BOOL groups)
563         if (groups) {
564                 //Keep groups in manual order
565                 if ([objectA orderIndex] > [objectB orderIndex]) {
566                         return NSOrderedDescending;
567                 } else {
568                         return NSOrderedAscending;
569                 }
570                 
571         } else {
572                 AIStatusSummary statusSummaryA = [objectA statusSummary];
573                 AIStatusSummary statusSummaryB = [objectB statusSummary];
574                 
575                 //Always sort offline contacts to the bottom
576                 BOOL onlineA = (statusSummaryA != AIOfflineStatus);
577                 BOOL onlineB = (statusSummaryB != AIOfflineStatus);
578                 if (!onlineB && onlineA) {
579                         return NSOrderedAscending;
580                 } else if (!onlineA && onlineB) {
581                         return NSOrderedDescending;
582                 }
583                 
584                 //We only need to start looking at status for sorting if both are online; 
585                 //otherwise, skip to resolving alphabetically or manually
586                 if (onlineA && onlineB) {
587                         unsigned int    i = 0;
588                         BOOL                    away[2];
589                         BOOL                    mobile[2];
590                         BOOL                    definitelyFinishedIfSuccessful, onlyIfWeAintGotNothinBetter, status;
591                         int                             idle[2];
592                         int                             sortIndex[2];
593                         int                             objectCounter;
594                         
595                         //Get the away state and idle times now rather than potentially doing each twice below
596                         away[0] = ((statusSummaryA == AIAwayStatus) || (statusSummaryA == AIAwayAndIdleStatus));
597                         away[1] = ((statusSummaryB == AIAwayStatus) || (statusSummaryB == AIAwayAndIdleStatus));
598                         
599                         idle[0] = (((statusSummaryA == AIIdleStatus) || (statusSummaryA == AIAwayAndIdleStatus)) ?
600                                            [objectA integerStatusObjectForKey:@"Idle" fromAnyContainedObject:NO] :
601                                            0);
602                         idle[1] = (((statusSummaryB == AIIdleStatus) || (statusSummaryB == AIAwayAndIdleStatus)) ?
603                                            [objectB integerStatusObjectForKey:@"Idle" fromAnyContainedObject:NO] :
604                                            0);
605                         
606                         if (groupMobile) {
607                                 mobile[0] = [objectA isMobile];
608                                 mobile[1] = [objectB isMobile];
609                         }
611                         for (objectCounter = 0; objectCounter < 2; objectCounter++) {
612                                 sortIndex[objectCounter] = 999;
614                                 for (i = 0; i < sizeOfSortOrder ; i++) {
615                                         //Reset the internal bookkeeping
616                                         onlyIfWeAintGotNothinBetter = NO;
617                                         definitelyFinishedIfSuccessful = NO;
618                                         
619                                         //Determine the state for the status this level of sorting cares about
620                                         switch (sortOrder[i]) {
621                                                 case Available:
622                                                         status = (!away[objectCounter] && !idle[objectCounter]); // TRUE if A is available
623                                                         break;
624                                                 
625                                                 case Mobile:
626                                                         status = mobile[objectCounter];
627                                                         break;
628                                                 
629                                                 case Away:
630                                                         status = away[objectCounter];
631                                                         break;
633                                                 case Idle:
634                                                         status = (idle[objectCounter] != 0);
635                                                         break;
637                                                 case Away_And_Idle:
638                                                         status =  away[objectCounter] && (idle[objectCounter] != 0);
639                                                         definitelyFinishedIfSuccessful = YES;
640                                                         break;
641                                                         
642                                                 case Unavailable:
643                                                         status =  away[objectCounter] || (idle[objectCounter] != 0);
644                                                         onlyIfWeAintGotNothinBetter = YES;
645                                                         break;
646                                                         
647                                                 case Online:
648                                                         status = YES; //we can only get here if the person is online, anyways
649                                                         onlyIfWeAintGotNothinBetter = YES;
650                                                         break;
651                                                 
652                                                 default:
653                                                         status = NO;
654                                         }
656                                         //If the object has the desired status and we want to use it, store the new index it should go to
657                                         if (status &&
658                                                 (!onlyIfWeAintGotNothinBetter || (sortIndex[objectCounter] == 999))) {
659                                                 sortIndex[objectCounter] = i;
660                                                 
661                                                 //If definitelyFinishedIfSuccessful is YES, we're done sorting as soon as something fits
662                                                 //this category
663                                                 if (definitelyFinishedIfSuccessful) break;
664                                         }
665                                 }
666                         } //End for object loop
667                         
668                         if (sortIndex[0] > sortIndex[1]) {
669                                 return NSOrderedDescending;
670                         } else if (sortIndex[1] > sortIndex[0]) {
671                                 return NSOrderedAscending;                      
672                         }
673                         
674                         //If one idle time is greater than the other and we want to sort on that basis, we have an ordering
675                         if (sortIdleTime) {
676                                 //Ordering is determined if either has a idle time and their idle times are not identical
677                                 if (((idle[0] != 0) || (idle[1] != 0)) && (idle[0] != idle[1])) {
678                                         if (idle[0] > idle[1]) {
679                                                 return NSOrderedDescending;
680                                         } else {
681                                                 return NSOrderedAscending;
682                                         }
683                                 }
684                         }
685                 }
686                 
687                 if (!resolveAlphabetically) {
688                         //If we don't want to resolve alphabetically, we do want to resolve by manual ordering if possible
689                         float orderIndexA = [objectA orderIndex];
690                         float orderIndexB = [objectB orderIndex];
691                         
692                         if (orderIndexA > orderIndexB) {
693                                 return NSOrderedDescending;
694                         } else if (orderIndexA < orderIndexB) {
695                                 return NSOrderedAscending;
696                         }
697                 }
698                 
699                 //If we made it here, resolve the ordering alphabetically, which is guaranteed to be consistent.
700                 //Note that this sort should -never- return NSOrderedSame, so as a last resort we use the internalObjectID.
701                 NSComparisonResult returnValue;
702                 
703                 if (resolveAlphabeticallyByLastName) {
704                         //Split the displayname into parts by spacing and use the last part, the "last name," for comparison
705                         NSString        *space = @" ";
706                         NSString        *displayNameA = [objectA displayName];
707                         NSString        *displayNameB = [objectB displayName];
708                         NSArray         *componentsA = [displayNameA componentsSeparatedByString:space];
709                         NSArray         *componentsB = [displayNameB componentsSeparatedByString:space];
710                         
711                         returnValue = [[componentsA lastObject] caseInsensitiveCompare:[componentsB lastObject]];
712                         //If the last names are the same, compare the whole object, which will amount to sorting these objects
713                         //by first name
714                         if (returnValue == NSOrderedSame) {
715                                 returnValue = [displayNameA caseInsensitiveCompare:displayNameB];
716                                 if (returnValue == NSOrderedSame) {
717                                         returnValue = [[objectA internalObjectID] caseInsensitiveCompare:[objectB internalObjectID]];
718                                 }
719                         }
720                 } else {
721                         returnValue = [[objectA longDisplayName] caseInsensitiveCompare:[objectB longDisplayName]];
722                         if (returnValue == NSOrderedSame) {
723                                 returnValue = [[objectA internalObjectID] caseInsensitiveCompare:[objectB internalObjectID]];
724                         }
725                 }
726                 
727                 return (returnValue);
728         }
732  * @brief Sort function
733  */
734 - (sortfunc)sortFunction{
735         return &statusSort;
738 @end