Retired Document
Important: This document may not represent best practices for current development. Links to downloads and other resources may no longer be valid.
SMFWindowController.m
/* |
IMPORTANT: This Apple software is supplied to you by Apple Computer, |
Inc. ("Apple") in consideration of your agreement to the following terms, |
and your use, installation, modification or redistribution of this Apple |
software constitutes acceptance of these terms. If you do not agree with |
these terms, please do not use, install, modify or redistribute this Apple |
software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following text |
and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Computer, |
Inc. may be used to endorse or promote products derived from the Apple |
Software without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES |
NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE |
IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A |
PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION |
ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND |
WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT |
LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY |
OF SUCH DAMAGE. |
*/ |
/* |
SMFWindowController.m |
SeeMyFriends |
Version: 1.1 |
Copyright (c) 2006 Apple Computer, Inc. All rights reserved. |
This controller manages the content of the main window, as well as interaction with SyncServices. It maintains |
a dictionary of all contacts (_allPeople) that is changed during sync phases and is used by the custom HIView |
to draw itself. |
*/ |
#import "SMFWindowController.h" |
#import "SMFPeopleView.h" |
static OSStatus SMFWindowControllerDispatch(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData); |
static OSStatus SMFSearchFieldEventHandler(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData); |
@implementation SMFWindowController |
#pragma mark -- LIFE CYCLE -- |
-(id) init |
{ |
OSStatus status; |
//init method inherits from NSObject |
self = [super init]; |
require(nil != self, EXIT); |
//storage for data |
_allPeople = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
require_action(NULL != _allPeople, EXIT, status = coreFoundationUnknownErr); |
//create search data |
_searchData = CFDataCreateMutable(kCFAllocatorDefault, 0); |
require_action(NULL != _searchData, EXIT, status = coreFoundationUnknownErr); |
_searchIndex = SKIndexCreateWithMutableData(_searchData, CFSTR("PeopleIndex"), kSKIndexInverted, NULL); |
require_action(NULL != _searchIndex, EXIT, status = coreFoundationUnknownErr); |
//create UI for application |
status = [self _initControllerWindowSide]; |
require_noerr(status, EXIT); |
//create needed sync information |
status = [self _initControllerSyncSide]; |
require_noerr(status, EXIT); |
EXIT: |
return ((noErr == status) ? self : nil); |
} |
- (OSStatus) _initControllerWindowSide |
{ |
IBNibRef nibRef; |
OSStatus status = noErr; |
HIViewRef scrollView, contentView; |
HIRect tmpRect; |
HILayoutInfo tmpLayout; |
EventRef event; |
HIViewID tmpViewID; |
SInt32 scrollBarSize; |
EventTypeSpec myWinCtlrEvents[] = {{ kEventClassWindow, kEventWindowShown}}; |
EventTypeSpec mySFieldEvents[] = { { kEventClassSearchField, kEventSearchFieldCancelClicked}, |
{ kEventClassTextField, kEventTextDidChange}, |
{ kEventClassTextField, kEventTextAccepted}}; |
//Create the window from the nib file |
status = CreateNibReference(CFSTR("main"), &nibRef); |
require_noerr( status, EXIT); |
status = CreateWindowFromNib(nibRef, CFSTR("MainWindow"), &_window); |
require_noerr( status, EXIT); |
DisposeNibReference(nibRef); |
//register for event on ShowWindow so we can sync when the window appear |
status = InstallWindowEventHandler (_window, SMFWindowControllerDispatch, GetEventTypeCount(myWinCtlrEvents), myWinCtlrEvents, (void *) self, NULL); |
require_noerr( status, EXIT); |
//Create content of window by adding views |
status = HIViewFindByID(HIViewGetRoot(_window), kHIViewWindowContentID, &contentView); |
require_noerr(status, EXIT); |
//Store the placard text in a object ivar |
tmpViewID.signature = 'sTex'; |
tmpViewID.id = 20; |
status = HIViewFindByID(contentView, tmpViewID, &_placardTextField); |
require_noerr(status, EXIT); |
//Add event for the search field |
tmpViewID.signature = 'sFld'; |
tmpViewID.id = 10; |
status = HIViewFindByID(contentView, tmpViewID, &_searchField); |
require_noerr(status, EXIT); |
status = HIViewInstallEventHandler(_searchField, SMFSearchFieldEventHandler, GetEventTypeCount(mySFieldEvents), mySFieldEvents, (void *) self, NULL); |
require_noerr( status, EXIT); |
//Create the scroll view |
status = HIScrollViewCreate(kHIScrollViewValidOptions, &scrollView); |
require_noerr( status, EXIT); |
//add an inset to show the placard. The following is not in any header, but has been discussed publicly on Carbon-help mailing list |
float hInset = 80; |
status = SetControlData( scrollView, kControlNoPart, 'hsin', sizeof( hInset ), &hInset ); |
require_noerr( status, EXIT); |
status = HIViewAddSubview(contentView, scrollView); |
require_noerr( status, EXIT); |
status = HIViewGetFrame(contentView, &tmpRect); |
require_noerr( status, EXIT); |
tmpRect.origin.y = 44; |
tmpRect.size.height -=44; |
status = HIViewSetFrame(scrollView,&tmpRect ); |
require_noerr( status, EXIT); |
tmpLayout.version = kHILayoutInfoVersionZero; |
status = HIViewGetLayoutInfo(scrollView, &tmpLayout); |
require_noerr( status, EXIT); |
tmpLayout.binding.top.toView = NULL; |
tmpLayout.binding.top.kind = kHILayoutBindMin; |
tmpLayout.binding.top.offset = 0.0; |
tmpLayout.binding.left.toView = NULL; |
tmpLayout.binding.left.kind = kHILayoutBindMin; |
tmpLayout.binding.left.offset = 0.0; |
tmpLayout.binding.bottom.toView = NULL; |
tmpLayout.binding.bottom.kind = kHILayoutBindMax; |
tmpLayout.binding.bottom.offset = 0.0; |
tmpLayout.binding.right.toView = NULL; |
tmpLayout.binding.right.kind = kHILayoutBindMax; |
tmpLayout.binding.right.offset = 0.0; |
status = HIViewSetLayoutInfo(scrollView, &tmpLayout); |
require_noerr( status, EXIT); |
// Then the content view. This is our custom HIView |
status = HIObjectCreate( kSMFPeopleViewClassID, NULL, (HIObjectRef*) &_matrixView ); |
require_noerr( status, EXIT ); |
status = HIViewAddSubview(scrollView, _matrixView); |
require_noerr( status, EXIT); |
tmpRect.origin.x = 0; |
tmpRect.origin.y = 0; |
tmpRect.size.height =400; |
tmpRect.size.width = 480; |
status = HIViewSetFrame(_matrixView,&tmpRect ); |
require_noerr( status, EXIT); |
status = HIViewPlaceInSuperviewAt(_matrixView, 0, 0); |
require_noerr( status, EXIT); |
status = HIViewSetVisible(scrollView, true); |
require_noerr( status, EXIT); |
status = HIViewSetVisible(_matrixView, true); |
require_noerr( status, EXIT); |
EXIT: |
return status; |
} |
- (OSStatus) _initControllerSyncSide |
{ |
OSStatus result = noErr; |
//Let's see if we have a client already |
_abSyncCatcher = [[ISyncManager sharedManager] clientWithIdentifier:(NSString *)SMFSyncClientID]; |
if(nil != _abSyncCatcher) { |
//Yes : we unarchive the data we need |
result = [self _unarchiveSyncData]; |
if(noErr != result) { |
//We had an error in unarchiving. Let's reset all data from truth. |
[_abSyncCatcher setShouldReplaceClientRecords:YES forEntityNames:[_abSyncCatcher supportedEntityNames]]; |
result = noErr; |
} |
} else { |
//This is the first time for this client, really. The sync description plist is called SMFSync and is located inside |
//the Resource folder of SeeMyFriend app bundle |
CFURLRef descriptionFileURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("SMFSync"), CFSTR("plist"), NULL); |
require_action(NULL != descriptionFileURL, EXIT, result = coreFoundationUnknownErr); |
CFStringRef descriptionFilePath = CFURLCopyFileSystemPath(descriptionFileURL, kCFURLPOSIXPathStyle); |
require_action(NULL != descriptionFilePath, EXIT, result = coreFoundationUnknownErr); |
//The real client creation |
_abSyncCatcher = [[ISyncManager sharedManager] registerClientWithIdentifier:(NSString *)SMFSyncClientID descriptionFilePath:(NSString *)descriptionFilePath]; |
require_quiet(nil != _abSyncCatcher, EXIT); // we should return an error if needed |
CFRelease(descriptionFilePath); |
CFRelease(descriptionFileURL); |
} |
//The sync handler will allow us to be called by the engine when another client is scyncing contact data |
[_abSyncCatcher setSyncAlertHandler:self selector:@selector(client:mightWantToSyncEntityNames:)]; |
EXIT: |
return result; |
} |
-(OSStatus) _unarchiveSyncData |
{ |
OSStatus result = noErr; |
FSRef apsUpRef, smfRefFolder, smfRefFile; |
CFURLRef smfURLFile = NULL; |
CFDataRef decodedData = NULL; |
HIArchiveRef decoder = NULL; |
CFArrayRef allPeopleKey = NULL; |
CFTypeRef * values, keys; |
int i, size; |
//create the URL for the archive file |
UniChar smfLogFolderName[]= {'S', 'e', 'e', 'M', 'y', 'F', 'r','i','e','n','d','s'}; |
UniChar smfLogFileName[]= {'S', 'y', 'n', 'c', 'D', 'a', 't','a'}; |
result = FSFindFolder(kUserDomain, kApplicationSupportFolderType, true, &apsUpRef); |
require_noerr(result, EXIT); |
result == FSMakeFSRefUnicode(&apsUpRef,12,smfLogFolderName,kTextEncodingUnknown,&smfRefFolder); |
require_noerr(result, EXIT); |
result == FSMakeFSRefUnicode(&smfRefFolder, 8,smfLogFileName,kTextEncodingUnknown,&smfRefFile); |
require_noerr(result, EXIT); |
smfURLFile = CFURLCreateFromFSRef(kCFAllocatorDefault,&smfRefFile); |
require_action(NULL!= smfURLFile, EXIT, result = coreFoundationUnknownErr); |
//get the archive |
Boolean tmpBool = CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, smfURLFile, &decodedData, NULL, NULL, NULL); |
require_action(tmpBool, EXIT, result = coreFoundationUnknownErr); |
result = HIArchiveCreateForDecoding(decodedData, 0, &decoder); |
require_noerr(result, EXIT); |
result = HIArchiveCopyDecodedCFType(decoder, CFSTR("syncData"), (CFTypeRef *)&allPeopleKey); |
require_noerr(result, EXIT); |
ISyncRecordSnapshot *tmpSnapshot= [[ISyncManager sharedManager] snapshotOfRecordsInTruthWithEntityNames:[_abSyncCatcher supportedEntityNames] usingIdentifiersForClient:_abSyncCatcher]; |
CFDictionaryRef tmpDico = (CFDictionaryRef) [[tmpSnapshot recordsWithIdentifiers:(NSArray *)allPeopleKey] retain]; |
size = CFDictionaryGetCount(tmpDico); |
values = (CFTypeRef *) malloc( size * sizeof(CFTypeRef) ); |
keys = (CFTypeRef *) malloc( size * sizeof(CFTypeRef) ); |
CFDictionaryGetKeysAndValues(tmpDico, (const void **) keys, (const void **) values); |
const void **theseKeys = (const void **) keys; |
for(i = 0; i < size; i++) { |
CFMutableDictionaryRef tmpOnePeopleDico = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
//Depending if there is a first name/last name we displays one, both of them... or <NO NAME> |
if(CFDictionaryContainsKey(values[i], CFSTR("first name"))) { |
if(CFDictionaryContainsKey(values[i], CFSTR("last name"))) { |
CFStringRef tmpString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ %@"), |
CFDictionaryGetValue(values[i], CFSTR("first name")), CFDictionaryGetValue(values[i], CFSTR("last name"))); |
CFDictionaryAddValue(tmpOnePeopleDico, kSMFFullNameKey, tmpString); |
CFRelease(tmpString); |
} else { |
CFDictionaryAddValue(tmpOnePeopleDico, kSMFFullNameKey, CFDictionaryGetValue(values[i], CFSTR("first name"))); |
} |
} else { |
if(CFDictionaryContainsKey(values[i], CFSTR("last name"))) { |
CFDictionaryAddValue(tmpOnePeopleDico, kSMFFullNameKey, CFDictionaryGetValue(values[i], CFSTR("last name"))); |
} else { |
CFDictionaryAddValue(tmpOnePeopleDico, kSMFFullNameKey, CFSTR("< NO NAME >")); |
} |
} |
//We get the image if it is here |
if(CFDictionaryContainsKey(values[i], CFSTR("image"))) { |
CFDataRef tmpData = CFDictionaryGetValue(values[i], CFSTR("image")); |
CGImageSourceRef tmpImageSource = CGImageSourceCreateWithData(tmpData, NULL); |
CGImageRef tmpImage = CGImageSourceCreateImageAtIndex(tmpImageSource, 0, NULL); |
CFDictionaryAddValue(tmpOnePeopleDico, kSMFPictureKey, tmpImage); |
CFRelease(tmpImageSource); |
CFRelease(tmpImage); |
} |
CFDictionaryAddValue(tmpOnePeopleDico, kSMFSearchedKey, kCFBooleanFalse); |
CFDictionaryAddValue(_allPeople, theseKeys[i], tmpOnePeopleDico); |
//we add the people to the search index |
SKDocumentRef aPeopleDocument = SKDocumentCreate(CFSTR("smfdoc"), NULL, (const void *) theseKeys[i]); |
if(true == SKIndexAddDocumentWithText(_searchIndex, aPeopleDocument, CFDictionaryGetValue(tmpOnePeopleDico,kSMFFullNameKey), 0)) |
SKIndexFlush(_searchIndex); |
CFRelease(aPeopleDocument); |
CFRelease(tmpOnePeopleDico); |
} |
//update the placard text |
CFStringRef tmpCountString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d Contacts"), CFDictionaryGetCount(_allPeople)); |
HIViewSetText(_placardTextField, tmpCountString); |
CFRelease(tmpCountString); |
free(theseKeys); |
free(values); |
EXIT: |
if(decoder) CFRelease(decoder); |
if(decodedData) CFRelease(decodedData); |
if(smfURLFile) CFRelease(smfURLFile); |
if(allPeopleKey) CFRelease(allPeopleKey); |
return result; |
} |
-(OSStatus) _archiveSyncData |
{ |
OSStatus result = noErr; |
CFDataRef smfData = NULL; |
CFURLRef smfURLFolder = NULL, smfURLFile = NULL; |
FSRef apsUpRef, smfRef; |
Boolean tmpBool; |
UniChar smfLogFolderName[]= {'S', 'e', 'e', 'M', 'y', 'F', 'r','i','e','n','d','s'}; |
HIArchiveRef encoder = NULL; |
CFMutableArrayRef allPeopleKeys = NULL; |
CFTypeRef * keys; |
int i, size; |
//get the place to store data |
result = FSFindFolder(kUserDomain, kApplicationSupportFolderType, true, &apsUpRef); |
require_noerr(result, EXIT); |
if( fnfErr == FSMakeFSRefUnicode(&apsUpRef,12,smfLogFolderName,kTextEncodingUnknown,&smfRef)) |
result = FSCreateDirectoryUnicode(&apsUpRef, 12, smfLogFolderName, kFSCatInfoNone, NULL, &smfRef, NULL, NULL); |
smfURLFolder = CFURLCreateFromFSRef(kCFAllocatorDefault,&smfRef); |
require_action(NULL!= smfURLFolder, EXIT, result = coreFoundationUnknownErr); |
smfURLFile = CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, smfURLFolder, CFSTR("SyncData"), false); |
require_action(NULL!= smfURLFile, EXIT, result = coreFoundationUnknownErr); |
//create the archive. |
result = HIArchiveCreateForEncoding(&encoder); |
require_noerr(result, EXIT); |
//Encode the data. We will just store UID so we avoid duplicating data from the truth |
allPeopleKeys = CFArrayCreateMutable(kCFAllocatorDefault, CFDictionaryGetCount(_allPeople), &kCFTypeArrayCallBacks); |
require_action(NULL!= allPeopleKeys, EXIT, result = coreFoundationUnknownErr); |
size = CFDictionaryGetCount(_allPeople); |
keys = (CFTypeRef *) malloc( size * sizeof(CFTypeRef) ); |
CFDictionaryGetKeysAndValues(_allPeople, (const void **) keys, NULL); |
const void **theseKeys = (const void **) keys; |
for(i = 0; i < size; i++) { |
CFArrayAppendValue(allPeopleKeys, theseKeys[i]); |
} |
result = HIArchiveEncodeCFType(encoder, CFSTR("syncData"), allPeopleKeys); |
require_noerr(result, EXIT); |
//store the file |
result = HIArchiveCopyEncodedData(encoder, &smfData); |
require_noerr(result, EXIT); |
CFURLWriteDataAndPropertiesToResource(smfURLFile, smfData, NULL, NULL); |
EXIT: |
if(allPeopleKeys) CFRelease(allPeopleKeys); |
if(encoder) CFRelease(encoder); |
if(smfData) CFRelease(smfData); |
if(smfURLFolder) CFRelease(smfURLFolder); |
if(smfURLFile) CFRelease(smfURLFile); |
return result; |
} |
- (void)dealloc |
{ |
OSStatus tmpResult = [self _archiveSyncData]; |
verify_noerr(tmpResult); |
//release everything |
if(_allPeople) |
CFRelease(_allPeople); |
if(_searchIndex) |
SKIndexClose(_searchIndex); |
if(_searchData) |
CFRelease(_searchData); |
[super dealloc]; |
} |
#pragma mark -- USEFUL ACCESORS -- |
-(CFDictionaryRef) allPeople |
{ |
return ((CFDictionaryRef) _allPeople); |
} |
#pragma mark -- SYNC METHODS -- |
- (void) doInitialSync |
{ |
//When the window is shown we trigger a sync. In order not to block the UI this is done in the background |
require(nil != _abSyncCatcher, EXIT); |
//UI shows a sync is happening |
[self setSyncingUISyncState:YES]; |
//Create a sync session |
[ISyncSession beginSessionInBackgroundWithClient:_abSyncCatcher entityNames:[_abSyncCatcher supportedEntityNames] target:self selector:@selector(bgSessionDoneWithClient:session:)]; |
EXIT: |
return; |
} |
-(void) bgSessionDoneWithClient:(ISyncClient *)cbClient session:(ISyncSession *)cbSession |
{ |
ISyncChange *change = nil; |
NSEnumerator* contactChangeEnumerator; |
require(nil != cbSession, EXIT); |
/* If we have a search cancel it */ |
[self cancelCurrentSearch]; |
/*This is where the core of the work is happening. We are getting all information in our local dictionary (_allPeople). |
Each entry dictionary is having for key the record Identifier and for value a dictionary containing first name, last name |
and image for the contact |
*/ |
//First we look if we are in a case wher ethe engine is pushing us all its truth. Here we support only one entity for syncing |
//so we pas it as a "constant string". A more complete app would inspect for each entity it supports |
if ([cbSession shouldReplaceAllRecordsOnClientForEntityName:@"com.apple.contacts.Contact"]) { |
CFDictionaryRemoveAllValues(_allPeople); |
} |
//second we get changes from the engines |
[cbSession prepareToPullChangesForEntityNames:[_abSyncCatcher supportedEntityNames] beforeDate:[NSDate distantFuture]]; |
contactChangeEnumerator = [cbSession changeEnumeratorForEntityNames:[_abSyncCatcher supportedEntityNames]]; |
while (nil != (change = [contactChangeEnumerator nextObject])) { |
CFDictionaryRef changeRecord = (CFDictionaryRef) [change record]; |
switch([change type]) { |
case ISyncChangeTypeAdd: |
{ |
//if this is add, we add it to the our local dictionary |
CFMutableDictionaryRef tmpDico = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
//Add the key for search result |
CFDictionaryAddValue(tmpDico, kSMFSearchedKey, kCFBooleanFalse); |
//Depending if there is a first name/last name we displays one, both of them... or <NO NAME> |
if(CFDictionaryContainsKey(changeRecord, CFSTR("first name"))) { |
if(CFDictionaryContainsKey(changeRecord, CFSTR("last name"))) { |
CFStringRef tmpString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ %@"), |
CFDictionaryGetValue(changeRecord, CFSTR("first name")), CFDictionaryGetValue(changeRecord, CFSTR("last name"))); |
CFDictionaryAddValue(tmpDico, kSMFFullNameKey, tmpString); |
CFRelease(tmpString); |
} else { |
CFDictionaryAddValue(tmpDico, kSMFFullNameKey, CFDictionaryGetValue(changeRecord, CFSTR("first name"))); |
} |
} else { |
if(CFDictionaryContainsKey(changeRecord, CFSTR("last name"))) { |
CFDictionaryAddValue(tmpDico, kSMFFullNameKey, CFDictionaryGetValue(changeRecord, CFSTR("last name"))); |
} else { |
CFDictionaryAddValue(tmpDico, kSMFFullNameKey, CFSTR("< NO NAME >")); |
} |
} |
//add it to the search index |
SKDocumentRef aPeopleDocument = SKDocumentCreate(CFSTR("smfdoc"), NULL, (CFStringRef) [change recordIdentifier]); |
if(true == SKIndexAddDocumentWithText(_searchIndex, aPeopleDocument, CFDictionaryGetValue(tmpDico,kSMFFullNameKey), 0)) |
SKIndexFlush(_searchIndex); |
CFRelease(aPeopleDocument); |
//We get the image if it is here |
if(CFDictionaryContainsKey(changeRecord, CFSTR("image"))) { |
CFDataRef tmpData = CFDictionaryGetValue(changeRecord, CFSTR("image")); |
CGImageSourceRef tmpImageSource = CGImageSourceCreateWithData(tmpData, NULL); |
CGImageRef tmpImage = CGImageSourceCreateImageAtIndex(tmpImageSource, 0, NULL); |
CFDictionaryAddValue(tmpDico, kSMFPictureKey, tmpImage); |
CFRelease(tmpImageSource); |
CFRelease(tmpImage); |
} |
CFDictionaryAddValue(_allPeople,(CFStringRef) [change recordIdentifier] , tmpDico); |
CFRelease(tmpDico); |
break; |
} |
case ISyncChangeTypeModify: |
{ |
//Similar to add but this is a modify. (We use CFDictionaryReplaceValue) |
CFMutableDictionaryRef tmpDico = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
CFDictionaryAddValue(tmpDico, kSMFSearchedKey, kCFBooleanFalse); |
if(CFDictionaryContainsKey(changeRecord, CFSTR("first name"))) { |
if(CFDictionaryContainsKey(changeRecord, CFSTR("last name"))) { |
CFStringRef tmpString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ %@"), |
CFDictionaryGetValue(changeRecord, CFSTR("first name")), CFDictionaryGetValue(changeRecord, CFSTR("last name"))); |
CFDictionaryAddValue(tmpDico, kSMFFullNameKey, tmpString); |
CFRelease(tmpString); |
} else { |
CFDictionaryAddValue(tmpDico, kSMFFullNameKey, CFDictionaryGetValue(changeRecord, CFSTR("first name"))); |
} |
} else { |
if(CFDictionaryContainsKey(changeRecord, CFSTR("last name"))) { |
CFDictionaryAddValue(tmpDico, kSMFFullNameKey, CFDictionaryGetValue(changeRecord, CFSTR("last name"))); |
} else { |
CFDictionaryAddValue(tmpDico, kSMFFullNameKey, CFSTR("< NO NAME >")); |
} |
} |
SKDocumentRef aPeopleDocument = SKDocumentCreate(CFSTR("smfdoc"), NULL, (CFStringRef) [change recordIdentifier]); |
if(true == SKIndexAddDocumentWithText(_searchIndex, aPeopleDocument, CFDictionaryGetValue(tmpDico,kSMFFullNameKey), 0)) |
SKIndexFlush(_searchIndex); |
CFRelease(aPeopleDocument); |
if(CFDictionaryContainsKey(changeRecord, CFSTR("image"))) { |
CFDataRef tmpData = CFDictionaryGetValue(changeRecord, CFSTR("image")); |
CGImageSourceRef tmpImageSource = CGImageSourceCreateWithData(tmpData, NULL); |
CGImageRef tmpImage = CGImageSourceCreateImageAtIndex(tmpImageSource, 0, NULL); |
CFDictionaryAddValue(tmpDico, kSMFPictureKey, tmpImage); |
CFRelease(tmpImageSource); |
CFRelease(tmpImage); |
} |
CFDictionaryReplaceValue(_allPeople,(CFStringRef) [change recordIdentifier] , tmpDico); |
CFRelease(tmpDico); |
break; |
} |
case ISyncChangeTypeDelete: |
{ |
//This is simply a delete. Let's remove the entry from search index and local dictionary. |
SKDocumentRef aPeopleDocument = SKDocumentCreate(CFSTR("smfdoc"), NULL, (CFStringRef) [change recordIdentifier]); |
if(true == SKIndexRemoveDocument(_searchIndex, aPeopleDocument)) |
SKIndexFlush(_searchIndex); |
CFRelease(aPeopleDocument); |
CFDictionaryRemoveValue(_allPeople, (CFStringRef) [change recordIdentifier]); |
break; |
} |
default: |
break; |
} |
//we acknowledge all changes |
[ cbSession clientAcceptedChangesForRecordWithIdentifier:[change recordIdentifier] formattedRecord:nil newRecordIdentifier:nil]; |
} |
//The sync session is finished |
[cbSession clientCommittedAcceptedChanges]; |
[cbSession finishSyncing]; |
//Let's update the UI |
[self setSyncingUISyncState:NO]; |
int tmpCount = CFDictionaryGetCount(_allPeople); |
SetControlData(_matrixView, 0, kControlSMFPeopleNumberTag, sizeof(int), &tmpCount); |
HIViewSetNeedsDisplay(_matrixView, YES); |
//update the placard text |
CFStringRef tmpCountString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d Contacts"), tmpCount); |
HIViewSetText(_placardTextField, tmpCountString); |
CFRelease(tmpCountString); |
EXIT: |
return; |
} |
-(void) client:(ISyncClient *)client mightWantToSyncEntityNames:(NSArray *)entityNames |
{ |
//This sync handler method is called by the engine when s sync is done on our entities |
int i, size; |
CFArrayRef supportedEntities = (CFArrayRef) [_abSyncCatcher supportedEntityNames]; |
CFRange supportedEntitiesRange = CFRangeMake(0, CFArrayGetCount(supportedEntities)); |
NSMutableArray *toSyncEntities = [[NSMutableArray alloc] initWithCapacity:0]; |
size = [entityNames count]; |
//we check all entity on entityNames are belonging to the one supported |
//However in this case this could be simpler as we support only one entity |
for(i = 0; i < size; i++) { |
if(CFArrayContainsValue(supportedEntities, supportedEntitiesRange, [entityNames objectAtIndex:i])) { |
[toSyncEntities addObject: (id) [entityNames objectAtIndex:i]]; |
} |
} |
if(0 != [toSyncEntities count]) { |
//we are interested in syncing so we acknowledge that by creating a sync session |
[self setSyncingUISyncState:YES]; |
[ISyncSession beginSessionInBackgroundWithClient:_abSyncCatcher entityNames:toSyncEntities target:self selector:@selector(bgSessionDoneWithClient:session:)]; |
} |
[toSyncEntities release]; |
} |
#pragma mark -- WINDOW/UI METHODS -- |
-(void) showWindow{ |
ShowWindow(_window); |
} |
- (void) setSyncingUISyncState: (BOOL) syncing |
{ |
HIViewID tmpViewID; |
HIViewRef tmpHIView, contentView; |
CFStringRef statusText = NULL; |
OSStatus status = noErr; |
//When a sync is occuring a spinning wheel is shown in the top right part of the window, as well as some visible text |
tmpViewID.signature = 'pBar'; |
tmpViewID.id = 10; |
status = HIViewFindByID(HIViewGetRoot(_window), kHIViewWindowContentID, &contentView); |
require_noerr(status, EXIT); |
status = HIViewFindByID(contentView, tmpViewID, &tmpHIView); |
require_noerr(status, EXIT); |
status = HIViewSetVisible(tmpHIView, (YES == syncing)); |
require_noerr(status, EXIT); |
tmpViewID.signature = 'sTex'; |
status = HIViewFindByID(contentView, tmpViewID, &tmpHIView); |
require_noerr(status, EXIT); |
statusText = (YES == syncing) ? CFSTR("Syncing...") : CFSTR("No sync operation"); |
status = HIViewSetText(tmpHIView, statusText); |
require_noerr(status, EXIT); |
EXIT: |
return; |
} |
#pragma mark -- LIVE SEARCH -- |
-(void) updateSearchedPeople |
{ |
SKSearchRef tmpSearch; |
CFIndex tmpIndex; |
SKDocumentID *resultID = (SKDocumentID *) calloc(10, sizeof(SKDocumentID)); |
CFStringRef candidateName = HIViewCopyText(_searchField); |
CFIndex idx; |
Boolean searchResult; |
HIMutableShapeRef tmpShape; |
HIRect currentRect; |
CFTypeRef * values; |
int i, size; |
tmpShape = HIShapeCreateMutable(); |
currentRect.origin.x = currentRect.origin.y = 0; |
currentRect.size.width = 120; |
currentRect.size.height = 60; |
//go back to clean state for search marker |
idx = 0; |
size = CFDictionaryGetCount(_allPeople); |
values = (CFTypeRef *) malloc( size * sizeof(CFTypeRef) ); |
CFDictionaryGetKeysAndValues(_allPeople, NULL, (const void **) values); |
for(i = 0; i < size; i++) { |
if(CFBooleanGetValue(CFDictionaryGetValue(values[i], kSMFSearchedKey))) { |
CFDictionarySetValue((CFMutableDictionaryRef) values[i], kSMFSearchedKey, kCFBooleanFalse); |
HIShapeRef localShape = HIShapeCreateWithRect(¤tRect); |
HIShapeUnion(tmpShape, localShape , tmpShape); |
CFRelease(localShape); |
} |
currentRect.origin.x+= kOnePeopleViewWidth; |
idx++; |
if(idx == kPeopleViewNbColumnInit) { |
idx = 0; |
currentRect.origin.x -= kPeopleViewNbColumnInit*kOnePeopleViewWidth; |
currentRect.origin.y+= kOnePeopleViewHeigth; |
} |
} |
//do the search (needs better error code) |
if(candidateName && CFStringGetLength(candidateName)) { |
CFStringRef searchString = CFStringCreateWithFormat(NULL, NULL, CFSTR("*%@*"),candidateName); |
SKIndexFlush(_searchIndex); |
tmpSearch = SKSearchCreate(_searchIndex, searchString, kSKSearchOptionNoRelevanceScores); |
CFStringRef *foundNames = (CFStringRef *) calloc(10, sizeof(CFStringRef)); |
do { |
searchResult = SKSearchFindMatches(tmpSearch, 10, resultID, NULL, 0, &tmpIndex); |
SKIndexCopyInfoForDocumentIDs(_searchIndex, tmpIndex, resultID, foundNames, NULL); |
for(idx = 0; idx < tmpIndex; idx++) { |
CFDictionarySetValue((CFMutableDictionaryRef) CFDictionaryGetValue(_allPeople, foundNames[idx]), kSMFSearchedKey, kCFBooleanTrue); |
} |
} while( true == searchResult); |
free(foundNames); |
CFRelease(searchString); |
CFRelease(tmpSearch); |
} |
free(resultID); |
idx = 0; |
currentRect.origin.x = currentRect.origin.y = 0; |
for(i = 0; i < size; i++) { |
if(CFBooleanGetValue(CFDictionaryGetValue(values[i], kSMFSearchedKey))) { |
HIShapeRef localShape = HIShapeCreateWithRect(¤tRect); |
HIShapeUnion(tmpShape, localShape , tmpShape); |
CFRelease(localShape); |
} |
currentRect.origin.x+= kOnePeopleViewWidth; |
idx++; |
if(idx == kPeopleViewNbColumnInit) { |
idx = 0; |
currentRect.origin.x -= kPeopleViewNbColumnInit*kOnePeopleViewWidth; |
currentRect.origin.y+= kOnePeopleViewHeigth; |
} |
} |
HIViewSetNeedsDisplayInShape(_matrixView, tmpShape, true); |
CFRelease(tmpShape); |
free(values); |
} |
-(void) cancelCurrentSearch |
{ |
HIMutableShapeRef tmpShape; |
HIRect currentRect; |
CFIndex idx; |
CFTypeRef * values; |
int i, size; |
tmpShape = HIShapeCreateMutable(); |
currentRect.origin.x = currentRect.origin.y = 0; |
currentRect.size.width = kOnePeopleViewWidth; |
currentRect.size.height = kOnePeopleViewHeigth; |
idx = 0; |
size = CFDictionaryGetCount(_allPeople); |
values = (CFTypeRef *) malloc( size * sizeof(CFTypeRef) ); |
CFDictionaryGetKeysAndValues(_allPeople, NULL, (const void **) values); |
for(i = 0; i < size; i++) { |
if(CFBooleanGetValue(CFDictionaryGetValue(values[i], kSMFSearchedKey))) { |
CFDictionarySetValue((CFMutableDictionaryRef) values[i], kSMFSearchedKey, kCFBooleanFalse); |
HIShapeRef localShape = HIShapeCreateWithRect(¤tRect); |
HIShapeUnion(tmpShape, localShape , tmpShape); |
CFRelease(localShape); |
} |
currentRect.origin.x+= kOnePeopleViewWidth; |
idx++; |
if(idx == kPeopleViewNbColumnInit) { |
idx = 0; |
currentRect.origin.x -= kPeopleViewNbColumnInit*kOnePeopleViewWidth; |
currentRect.origin.y+= kOnePeopleViewHeigth; |
} |
} |
HIViewSetNeedsDisplayInShape(_matrixView, tmpShape, true); |
HIViewSetText(_searchField , CFSTR("")); |
CFRelease(tmpShape); |
} |
@end |
#pragma mark -- EVENT HANDLERS -- |
static OSStatus SMFWindowControllerDispatch(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData) |
{ |
OSStatus result = eventNotHandledErr; |
SMFWindowController *tmpSMFWinCtler = (SMFWindowController *)inUserData; |
Boolean tmpBool; |
//This event handler on the window will launch a sync when the apps starts |
if(kEventClassWindow == GetEventClass(inEvent)) { |
switch(GetEventKind(inEvent)) { |
case kEventWindowShown: |
[tmpSMFWinCtler doInitialSync]; |
break; |
default: |
break; |
} |
} |
EXIT: |
return result; |
} |
static OSStatus SMFSearchFieldEventHandler(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData) |
{ |
OSStatus result = eventNotHandledErr; |
SMFWindowController *tmpSMFWinCtler = (SMFWindowController *)inUserData; |
if (kEventClassSearchField == GetEventClass(inEvent)) { |
switch(GetEventKind(inEvent)) { |
case kEventSearchFieldCancelClicked: |
[tmpSMFWinCtler cancelCurrentSearch]; |
break; |
default: |
break; |
} |
} else if(kEventClassTextField == GetEventClass(inEvent)) { |
switch(GetEventKind(inEvent)) { |
case kEventTextDidChange: |
[tmpSMFWinCtler updateSearchedPeople]; |
break; |
default: |
break; |
} |
} |
return result; |
} |
Copyright © 2006 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2006-10-16