Using {{{setDefaultPermitDenyForAccount}}} on the gaim thread. Changed an autoreleas...
[adiumx.git] / Source / ESStatusSort.m
blobe6885d28cfbf1e3f23b5b1834cd9232fbb0c4056
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 "AIContactController.h"
18 #import "AIPreferenceController.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         
128         unsigned int i;
129         
130         for (i = 0; i < MAX_SORT_ORDER_DIMENSION; i++){
131                 sortOrder[i] = -1;
132         }
133         
134         i = 0;
135         
136         //Enumerate the ordering array.  For all sort types which are valid given the active sorting types,
137         //add to sortOrder[].  Finalize sortOrder with -1.
138         
139         BOOL    groupIdleOrIdleTime = (groupIdle || sortIdleTime);
141         enumerator = [sortOrderArray objectEnumerator];
142         while (sortTypeNumber = [enumerator nextObject]){
143                 switch ([sortTypeNumber intValue]){
144                         case Available: 
145                                 /* Group available if:
146                                         Group available,
147                                         Group all unavailable, or 
148                                         Group separetely the idle and the away (such that the remaining alternative is Available)
149                                 */
150                                 if (groupAvailable || 
151                                         groupUnavailable ||
152                                         (/*!groupUnavailable &&*/ groupAway && groupIdleOrIdleTime)) sortOrder[i++] = Available;
153                                 break;
154                                 
155                         case Away:
156                                 if (!groupUnavailable && groupAway) sortOrder[i++] = Away;
157                                 break;
158                                 
159                         case Idle:
160                                 if ((!groupUnavailable && groupIdle) || sortIdleTime) sortOrder[i++] = Idle;
161                                 break;
162                                 
163                         case Away_And_Idle:
164                                 if (!groupUnavailable && groupIdleAndAway) sortOrder[i++] = Away_And_Idle;
165                                 break;
166                                 
167                         case Unavailable: 
168                                 //If one of groupAway or groupIdle is off, or we need a generic unavailable sort
169                                 if (groupUnavailable ||
170                                         ((groupAvailable && (!groupAway || !groupIdleOrIdleTime)))){
171                                         sortOrder[i++] = Unavailable;
172                                 }
173                                 break;
174                                 
175                         case Online:
176                                 /* Show Online category if:
177                                         We aren't grouping all the available ones (this would imply grouping unavailable)
178                                         We aren't grouping all the unavailable ones (this would imply grouping available)
179                                         We aren't grouping both the away and the idle ones (this would imply grouping available)
180                                 */
181                                 if (!groupAvailable && !groupUnavailable && !(groupAway && (groupIdleOrIdleTime))){
182                                         sortOrder[i++] = Online;
183                                 }
184                                 break;
185                                 
186                         case Mobile:
187                                 if (groupAvailable && groupMobile){
188                                         sortOrder[i++] = Mobile;
189                                 }
190                                 break;
191                 }
192         }
193         
194         sortOrder[i] = -1;
195         
196         sizeOfSortOrder = i;
197         
198         [tableView_sortOrder reloadData];
202  * @brief Non-localized identifier
203  */
204 - (NSString *)identifier{
205     return(@"by Status");
209  * @brief Localized display name
210  */
211 - (NSString *)displayName{
212     return(AILocalizedString(@"Sort Contacts by Status",nil));
216  * @brief Status keys which, when changed, should trigger a resort
217  */
218 - (NSSet *)statusKeysRequiringResort{
219         return([NSSet setWithObjects:@"Online",@"Idle",@"StatusType",@"IsMobile",nil]);
223  * @brief Attribute keys which, when changed, should trigger a resort
224  */
225 - (NSSet *)attributeKeysRequiringResort{
226         return([NSSet setWithObject:@"Display Name"]);
229 //Configuration
230 #pragma mark Configuration
232  * @brief Window title when configuring the sort
234  * Subclasses should provide a title for configuring the sort only if configuration is possible.
235  * @result Localized title. If nil, the menu item will be disabled.
236  */
237 - (NSString *)configureSortWindowTitle{
238         return(AILocalizedString(@"Configure Status Sort",nil));        
242  * @brief Nib name for configuration
243  */
244 - (NSString *)configureNibName{
245         return @"StatusSortConfiguration";
249  * @brief View did load
250  */
251 - (void)viewDidLoad
253         [checkBox_groupAvailable setState:groupAvailable];
254         [checkBox_groupMobileSeparately setState:groupMobile];
255         [checkBox_groupAway setState:groupAway];
256         [checkBox_groupIdle setState:groupIdle];
257         [checkBox_groupIdleAndAway setState:groupIdleAndAway];
258         [checkBox_sortIdleTime setState:sortIdleTime];
259         [checkBox_alphabeticallyByLastName setState:resolveAlphabeticallyByLastName];
260         
261         [buttonCell_alphabetically setState:(resolveAlphabetically ? NSOnState : NSOffState)];
262         [buttonCell_manually setState:(resolveAlphabetically ? NSOffState : NSOnState)];
264         [buttonCell_allUnavailable setState:(groupUnavailable ? NSOnState : NSOffState)];
265         [buttonCell_separateUnavailable setState:(groupUnavailable ? NSOffState : NSOnState)];
266         
267         [self configureControlDimming];
268         
269         [tableView_sortOrder setDataSource:self];
270         [tableView_sortOrder setDelegate:self];
271     [tableView_sortOrder registerForDraggedTypes:[NSArray arrayWithObject:STATUS_DRAG_TYPE]];
275  * @brief Preference changed
277  * Sort controllers should live update as preferences change.
278  */
279 - (IBAction)changePreference:(id)sender
281         NSArray *sortOrderArray =  [[adium preferenceController] preferenceForKey:KEY_SORT_ORDER
282                                                                                                                                                 group:PREF_GROUP_CONTACT_SORTING];
283         if (sender == checkBox_groupAvailable){
284                 groupAvailable = [sender state];
285                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:groupAvailable]
286                                              forKey:KEY_GROUP_AVAILABLE
287                                               group:PREF_GROUP_CONTACT_SORTING];
289                 [self configureControlDimming];
290                 
291         }else if (sender == checkBox_groupMobileSeparately){
292                 groupMobile = [sender state];
293                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:groupMobile]
294                                              forKey:KEY_GROUP_MOBILE
295                                               group:PREF_GROUP_CONTACT_SORTING];                
296                 
297                 //Ensure the mobile item is in our sort order array, as the old defaults didn't include it
298                 if([sortOrderArray indexOfObject:[NSNumber numberWithInt:Mobile]] == NSNotFound){
299                         NSMutableArray  *newSortOrderArray = [[sortOrderArray mutableCopy] autorelease];
300                         [newSortOrderArray addObject:[NSNumber numberWithInt:Mobile]];
301                         
302                         [[adium preferenceController] setPreference:newSortOrderArray
303                                                                                                  forKey:KEY_SORT_ORDER
304                                                                                                   group:PREF_GROUP_CONTACT_SORTING];
305                         
306                         sortOrderArray = newSortOrderArray;
307                 }
308                 
309         }else if (sender == checkBox_groupAway){
310                 groupAway = [sender state];
311                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:groupAway]
312                                              forKey:KEY_GROUP_AWAY
313                                               group:PREF_GROUP_CONTACT_SORTING];                
314         }else if (sender == checkBox_groupIdle){
315                 groupIdle = [sender state];
316                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:groupIdle]
317                                              forKey:KEY_GROUP_IDLE
318                                               group:PREF_GROUP_CONTACT_SORTING];
319                 
320         }else if (sender == checkBox_groupIdleAndAway){
321                 groupIdleAndAway = [sender state];
322                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:groupIdleAndAway]
323                                              forKey:KEY_GROUP_IDLE_AND_AWAY
324                                               group:PREF_GROUP_CONTACT_SORTING];
325                 
326         }else if (sender == checkBox_sortIdleTime){
327                 sortIdleTime = [sender state];
328                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:sortIdleTime]
329                                              forKey:KEY_SORT_IDLE_TIME
330                                               group:PREF_GROUP_CONTACT_SORTING];                                
331         }else if(sender == matrix_resolution){
332                 id selectedCell = [sender selectedCell];
333                 
334                 resolveAlphabetically = (selectedCell == buttonCell_alphabetically);
335                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:resolveAlphabetically]
336                                                                                          forKey:KEY_RESOLVE_ALPHABETICALLY
337                                                                                           group:PREF_GROUP_CONTACT_SORTING];
338                 
339                 [self configureControlDimming];
340                 
341         }else if(sender == matrix_unavailableGrouping){
342                 id selectedCell = [sender selectedCell];
343                 
344                 groupUnavailable = (selectedCell == buttonCell_allUnavailable);
345                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:groupUnavailable]
346                                                                                          forKey:KEY_GROUP_UNAVAILABLE
347                                                                                           group:PREF_GROUP_CONTACT_SORTING];
348                 
349                 [self configureControlDimming];
350                 
351         }else if (sender == checkBox_alphabeticallyByLastName){
352                 resolveAlphabeticallyByLastName = [sender state];
353                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:resolveAlphabeticallyByLastName]
354                                              forKey:KEY_RESOLVE_BY_LAST_NAME
355                                               group:PREF_GROUP_CONTACT_SORTING];
356         }
357         
358         [self pruneAndSetSortOrderFromArray:sortOrderArray];
359         
360         [[adium contactController] sortContactList];
364  * @brief Configure control dimming
365  */
366 - (void)configureControlDimming
368         [checkBox_alphabeticallyByLastName setEnabled:resolveAlphabetically];
369         [checkBox_groupAway setEnabled:!groupUnavailable];
370         [checkBox_groupIdle setEnabled:!groupUnavailable];
371         [checkBox_groupIdleAndAway setEnabled:!groupUnavailable];
372         
373         [checkBox_groupMobileSeparately setEnabled:groupAvailable];
376 #pragma mark Sort Order Tableview datasource
378  * @brief Table view number of rows
379  */
380 - (int)numberOfRowsInTableView:(NSTableView *)tableView
382         return sizeOfSortOrder;
386  * @brief Table view object value
387  */
388 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
390         switch (sortOrder[rowIndex]){
391                 case Available:
392                         return AVAILABLE;
393                         break;
394                         
395                 case Away:
396                         return AWAY;
397                         break;
398                         
399                 case Idle:
400                         return IDLE;
401                         break;
402                         
403                 case Away_And_Idle:
404                         return AWAY_AND_IDLE;
405                         break;
406                         
407                 case Unavailable:
408                         //Unavailable is always the same sort, but to the user it can be either "Unavailable" or "Other Unavailable"
409                         //depending upon what other options are active.  The test here is purely cosmetic.
410                         return ((!sortIdleTime && (groupUnavailable || !(groupAway || groupIdle || groupIdleAndAway))) ?
411                                         UNAVAILABLE :
412                                         OTHER_UNAVAILABLE);
413                         break;
414                 
415                 case Online:
416                         return ONLINE;
417                         break;
418                         
419                 case Mobile:
420                         return MOBILE;
421                         break;
422         }
423         
424         return @"";
428  * @brief The NSNumber Status_Sort_Type which corresponds to a string
430  * @param string A string such as AVAILABLE or AWAY (localized)
431  * @result The NSNumber Status_Sort_Type which corresponds to the string 
432  */
433 - (NSNumber *)numberForString:(NSString *)string
435         int equivalent = -1;
437         if ([string isEqualToString:AVAILABLE]){
438                 equivalent = Available;
439         }else if ([string isEqualToString:AWAY]){
440                 equivalent = Away;
441         }else if ([string isEqualToString:IDLE]){
442                 equivalent = Idle;
443         }else if ([string isEqualToString:AWAY_AND_IDLE]){
444                 equivalent = Away_And_Idle;
445         }else if ([string isEqualToString:UNAVAILABLE] || ([string isEqualToString:OTHER_UNAVAILABLE])){
446                 equivalent = Unavailable;
447         }else if ([string isEqualToString:ONLINE]){
448                 equivalent = Online;
449         }else if([string isEqualToString:MOBILE]){
450                 equivalent = Mobile;
451         }
452         
453         return [NSNumber numberWithInt:equivalent];
457  * @brief Table view write rows
458  */
459 -  (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray *)rows toPasteboard:(NSPasteboard *)pboard
461     [pboard declareTypes:[NSArray arrayWithObject:STATUS_DRAG_TYPE] owner:self];
462         
463     //Build a list of all the highlighted aways
464     NSString    *dragItem = [self tableView:tableView
465                                   objectValueForTableColumn:nil
466                                                                                 row:[[rows objectAtIndex:0] intValue]];
467         
468     //put it on the pasteboard
469     [pboard setString:dragItem forType:STATUS_DRAG_TYPE];
470         
471     return(YES);
475  * @brief Table view validate drop
476  */
477 - (NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
479     NSString    *avaliableType = [[info draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObject:STATUS_DRAG_TYPE]];
481         if([avaliableType isEqualToString:STATUS_DRAG_TYPE]){
482         if(operation == NSTableViewDropAbove && row != -1){
483             return(NSDragOperationMove);
484         }else{
485             return(NSDragOperationNone);
486                 }
487         }
488         
489     return(NSDragOperationNone);
493  * @brief Table view accept drop
494  */
495 - (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
497     NSString            *availableType = [[info draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObject:STATUS_DRAG_TYPE]];
499     if([availableType isEqualToString:STATUS_DRAG_TYPE]){
500                 NSString                *item = [[info draggingPasteboard] stringForType:STATUS_DRAG_TYPE];
501                 
502                 //Remember, sortOrderPref contains all possible sorting types, not just the ones presently visible in the table!
503                 NSMutableArray  *sortOrderPref = [[[adium preferenceController] preferenceForKey:KEY_SORT_ORDER
504                                                                                                                                                                    group:PREF_GROUP_CONTACT_SORTING] mutableCopy];
505                 NSNumber                *sortNumber = [self numberForString:item];
506                 
507                 //Remove it from our array
508                 [sortOrderPref removeObject:sortNumber];
509                 
510                 if (row == [tableView numberOfRows]){
511                         //Dropped at the bottom
512                         [sortOrderPref addObject:sortNumber];
513                 }else{
514                         //Find the object which will end up just below it
515                         int targetIndex = [sortOrderPref indexOfObject:[self numberForString:[self tableView:tableView
516                                                                                                                                                  objectValueForTableColumn:nil
517                                                                                                                                                                                            row:row]]];
518                         if (targetIndex != NSNotFound){
519                                 //Insert it there
520                                 [sortOrderPref insertObject:sortNumber atIndex:targetIndex];
521                         }else{
522                                 //Dropped at the bottom
523                                 [sortOrderPref addObject:sortNumber];
524                         }
525                 }
526                 
527                 [[adium preferenceController] setPreference:sortOrderPref
528                                                                                          forKey:KEY_SORT_ORDER
529                                                                                           group:PREF_GROUP_CONTACT_SORTING];
530                 
531                 [self pruneAndSetSortOrderFromArray:sortOrderPref];             
532                 
533                 //Select and scroll to the dragged object
534                 [tableView reloadData];
535                 
536                 [[adium contactController] sortContactList];
538                 [sortOrderPref release];
539         }
540         
541         
542     return(YES);
546 #pragma mark Sorting
549  * @brief The status sort method itself
551  * It's magic... but it's efficient magic!
552  */
553 int statusSort(id objectA, id objectB, BOOL groups)
555         if(groups){
556                 //Keep groups in manual order
557                 if([objectA orderIndex] > [objectB orderIndex]){
558                         return(NSOrderedDescending);
559                 }else{
560                         return(NSOrderedAscending);
561                 }
562                 
563         }else{
564                 AIStatusSummary statusSummaryA = [objectA statusSummary];
565                 AIStatusSummary statusSummaryB = [objectB statusSummary];
566                 
567                 //Always sort offline contacts to the bottom
568                 BOOL onlineA = (statusSummaryA != AIOfflineStatus);
569                 BOOL onlineB = (statusSummaryB != AIOfflineStatus);
570                 if (!onlineB && onlineA){
571                         return NSOrderedAscending;
572                 }else if (!onlineA && onlineB){
573                         return NSOrderedDescending;
574                 }
575                 
576                 //We only need to start looking at status for sorting if both are online; 
577                 //otherwise, skip to resolving alphabetically or manually
578                 if (onlineA && onlineB){
579                         unsigned int    i = 0;
580                         BOOL                    away[2];
581                         BOOL                    mobile[2];
582                         BOOL                    definitelyFinishedIfSuccessful, onlyIfWeAintGotNothinBetter, status;
583                         int                             idle[2];
584                         int                             sortIndex[2];
585                         int                             objectCounter;
586                         
587                         //Get the away state and idle times now rather than potentially doing each twice below
588                         away[0] = ((statusSummaryA == AIAwayStatus) || (statusSummaryA == AIAwayAndIdleStatus));
589                         away[1] = ((statusSummaryB == AIAwayStatus) || (statusSummaryB == AIAwayAndIdleStatus));
590                         
591                         idle[0] = (((statusSummaryA == AIIdleStatus) || (statusSummaryA == AIAwayAndIdleStatus)) ?
592                                            [objectA integerStatusObjectForKey:@"Idle" fromAnyContainedObject:NO] :
593                                            0);
594                         idle[1] = (((statusSummaryB == AIIdleStatus) || (statusSummaryB == AIAwayAndIdleStatus)) ?
595                                            [objectB integerStatusObjectForKey:@"Idle" fromAnyContainedObject:NO] :
596                                            0);
597                         
598                         if(groupMobile){
599                                 mobile[0] = [objectA isMobile];
600                                 mobile[1] = [objectB isMobile];
601                         }
603                         for (objectCounter = 0; objectCounter < 2; objectCounter++){
604                                 sortIndex[objectCounter] = 999;
606                                 for (i = 0; i < sizeOfSortOrder ; i++){
607                                         //Reset the internal bookkeeping
608                                         onlyIfWeAintGotNothinBetter = NO;
609                                         definitelyFinishedIfSuccessful = NO;
610                                         
611                                         //Determine the state for the status this level of sorting cares about
612                                         switch (sortOrder[i]){
613                                                 case Available:
614                                                         status = (!away[objectCounter] && !idle[objectCounter]); // TRUE if A is available
615                                                         break;
616                                                 
617                                                 case Mobile:
618                                                         status = mobile[objectCounter];
619                                                         break;
620                                                 
621                                                 case Away:
622                                                         status = away[objectCounter];
623                                                         break;
625                                                 case Idle:
626                                                         status = (idle[objectCounter] != 0);
627                                                         break;
629                                                 case Away_And_Idle:
630                                                         status =  away[objectCounter] && (idle[objectCounter] != 0);
631                                                         definitelyFinishedIfSuccessful = YES;
632                                                         break;
633                                                         
634                                                 case Unavailable:
635                                                         status =  away[objectCounter] || (idle[objectCounter] != 0);
636                                                         onlyIfWeAintGotNothinBetter = YES;
637                                                         break;
638                                                         
639                                                 case Online:
640                                                         status = YES; //we can only get here if the person is online, anyways
641                                                         onlyIfWeAintGotNothinBetter = YES;
642                                                         break;
643                                                 
644                                                 default:
645                                                         status = NO;
646                                         }
648                                         //If the object has the desired status and we want to use it, store the new index it should go to
649                                         if (status &&
650                                                 (!onlyIfWeAintGotNothinBetter || (sortIndex[objectCounter] == 999))){
651                                                 sortIndex[objectCounter] = i;
652                                                 
653                                                 //If definitelyFinishedIfSuccessful is YES, we're done sorting as soon as something fits
654                                                 //this category
655                                                 if (definitelyFinishedIfSuccessful) break;
656                                         }
657                                 }
658                         } //End for object loop
659                         
660                         if (sortIndex[0] > sortIndex[1]){
661                                 return NSOrderedDescending;
662                         }else if (sortIndex[1] > sortIndex[0]){
663                                 return NSOrderedAscending;                      
664                         }
665                         
666                         //If one idle time is greater than the other and we want to sort on that basis, we have an ordering
667                         if (sortIdleTime){
668                                 //Ordering is determined if either has a idle time and their idle times are not identical
669                                 if (((idle[0] != 0) || (idle[1] != 0)) && (idle[0] != idle[1])){
670                                         if(idle[0] > idle[1]){
671                                                 return(NSOrderedDescending);
672                                         }else{
673                                                 return(NSOrderedAscending);
674                                         }
675                                 }
676                         }
677                 }
678                 
679                 if (!resolveAlphabetically){
680                         //If we don't want to resolve alphabetically, we do want to resolve by manual ordering if possible
681                         float orderIndexA = [objectA orderIndex];
682                         float orderIndexB = [objectB orderIndex];
683                         
684                         if(orderIndexA > orderIndexB){
685                                 return(NSOrderedDescending);
686                         }else if (orderIndexA < orderIndexB){
687                                 return(NSOrderedAscending);
688                         }
689                 }
690                 
691                 //If we made it here, resolve the ordering alphabetically, which is guaranteed to be consistent.
692                 //Note that this sort should -never- return NSOrderedSame, so as a last resort we use the internalObjectID.
693                 NSComparisonResult returnValue;
694                 
695                 if (resolveAlphabeticallyByLastName){
696                         //Split the displayname into parts by spacing and use the last part, the "last name," for comparison
697                         NSString        *space = @" ";
698                         NSString        *displayNameA = [objectA displayName];
699                         NSString        *displayNameB = [objectB displayName];
700                         NSArray         *componentsA = [displayNameA componentsSeparatedByString:space];
701                         NSArray         *componentsB = [displayNameB componentsSeparatedByString:space];
702                         
703                         returnValue = [[componentsA lastObject] caseInsensitiveCompare:[componentsB lastObject]];
704                         //If the last names are the same, compare the whole object, which will amount to sorting these objects
705                         //by first name
706                         if (returnValue == NSOrderedSame){
707                                 returnValue = [displayNameA caseInsensitiveCompare:displayNameB];
708                                 if (returnValue == NSOrderedSame){
709                                         returnValue = [[objectA internalObjectID] caseInsensitiveCompare:[objectB internalObjectID]];
710                                 }
711                         }
712                 }else{
713                         returnValue = [[objectA longDisplayName] caseInsensitiveCompare:[objectB longDisplayName]];
714                         if (returnValue == NSOrderedSame){
715                                 returnValue = [[objectA internalObjectID] caseInsensitiveCompare:[objectB internalObjectID]];
716                         }
717                 }
718                 
719                 return (returnValue);
720         }
724  * @brief Sort function
725  */
726 - (sortfunc)sortFunction{
727         return(&statusSort);
730 @end