Sign In/My Account | View Cart  

advertisement

AddThis Social Bookmark Button

Article:
  Creating Toolbars for Mac OS X
Subject:   Synchronization
Date:   2002-03-20 20:16:20
From:   michele
Below a solution (maybe not the finest one) to get synchronization between records and display.


First a validateRecord method to avoid empty records called before adding and inserting records:


- (BOOL) validateRecord
{
if ([[firstNameField stringValue] isEqualToString: @""])
{
[firstNameField setStringValue: @"Enter a first name, please"];
[firstNameField selectText: self];
return NO;
}
else if ([[lastNameField stringValue] isEqualToString: @""])
{
[lastNameField setStringValue: @"Enter a last name, please"];
[lastNameField selectText: self];
return NO;
}


return YES;
}


Better solution would be to test for having only printable characters, but for now I don't know how to do that. Plus test for a valid email address if any. Any hint?


Then some changes in the deleteRecord method (second part): I've introduced a dummy string for replacing all objects to be deleted by this special string. It is the only way I've found not to delete identical records, as I do not test if a new record is identical to an already stored record before saving it.


Better solution would be to alert the user when a record identical to another is about to be saved.


Then I've added an outlet to the searchText field, to retrieve it easily in any case (not just when the user changes its content).


I've changed the controlTextDidChange method and added three other methods for synchronizing records and selected records in any case. Hence I've changed the addRecord, insertRecord, sendMail, deleteRecord methods accordingly.


- (IBAction) addRecord: (id) sender
{
// Check the validity of record fields
if ([self validateRecord])
{
// Call create record to create the dictionary
// Then adds the dictionary to the end of the array
// The array retains the dictionary
[records addObject: [self createRecord]];


// Synchronize display
[self synchronizeView];


// Update the contents of the table after changes
[tableView reloadData];


// Save data to file
[self saveData];


// Clear the fields
[self clearField];
}
}



// Delete with a sheet - second part
- (void) sheetDidEnd: (NSWindow *) sheet
returnCode: (int) returnCode
contextInfo: (void *) contextInfo
{
NSEnumerator *enumerator; // For enumerating the selected rows
NSNumber *index; // For the NSNumber returned by nextObject
int realIndex;
NSString *delete = @".";


// Remove if OK
if ( returnCode == NSAlertDefaultReturn )
{
// Creates the enumerator
enumerator = [tableView selectedRowEnumerator];


// Traverse the enumerator
while ( (index = [enumerator nextObject]) )
{
// Retrieves the object to be deleted
realIndex = [records indexOfObjectIdenticalTo:[activeSet objectAtIndex: [index intValue]]];


// Replace the object at index with null value
[records replaceObjectAtIndex: realIndex withObject: delete];
}


// Remove all objects identical to delete
[records removeObject: delete];


// Deselect the rows
[tableView deselectAll: self];


// Synchronize display
[self synchronizeView];


// Updates the table contents
[tableView reloadData];


// Save data to file
[self saveData];
}

}


- (IBAction)insertRecord:(id)sender
{
// Validate the record
if ([self validateRecord])
{
// Retrieve the index of the selected row in the display view
int index = [tableView selectedRow];


if (index != -1)
{


// Retrieve the corresponding index in the records file
int realIndex = [records indexOfObjectIdenticalTo:[activeSet objectAtIndex: index]];


// Insert the dictionary before the selected row
[records insertObject: [self createRecord] atIndex: realIndex];


// Synchronize display
[self synchronizeView];


// Update the table contents
[tableView reloadData];


// Save data to file
[self saveData];


// Clear the fields
[self clearField];
}
}
}


// For sending mail
- (IBAction) sendMail: (id) sender
{
NSMutableString *url = [NSMutableString stringWithString: @"mailto:"];
NSString *str;
NSEnumerator *enumerator;
NSNumber *index;
int realIndex;


// Check if there is a selected row
if ([tableView numberOfSelectedRows] == 0)
{
return;
}


// Enumerate all selected rows
enumerator = [tableView selectedRowEnumerator];


// Traverse the selected rows with enumerator
while ( ( index = [enumerator nextObject] ) )
{
// Retrieve the email field
realIndex = [records indexOfObjectIdenticalTo:[activeSet objectAtIndex: [index intValue]]];
str = [[records objectAtIndex: realIndex]
objectForKey: @"Email"];
// Add a comma to separate email addresses if this is not the first one
if (![url isEqualToString: @"mailto:"])
{
[url appendString: @","];
}
// Add the email address to the previously built string
[url appendString: str];
}

// Send the whole string to the application declared as email client
[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: url]];
}
- (void) controlTextDidChange: (NSNotification *) aNotification
{
NSString *searchString = [[[aNotification object] stringValue] lowercaseString];
NSMutableArray *selectedRecords = [self retrieveSelectedRecords];
[self synchronizeRecord: searchString];
[self synchronizeSelection: selectedRecords];
[tableView reloadData];
}


- (void) synchronizeView
{
NSString *searchString = [[searchText stringValue] lowercaseString];
NSMutableArray *selectedRecords = [self retrieveSelectedRecords];
[self synchronizeRecord: searchString];
[self synchronizeSelection: selectedRecords];
}


- (NSMutableArray *) retrieveSelectedRecords
{
NSMutableArray *tempArray;
NSEnumerator *e;
NSNumber *index;
unsigned realIndex;


// Create a temporary array to store the selected records
tempArray = [NSMutableArray array];


// Enumerate all selected rows
e = [tableView selectedRowEnumerator];


// Traverse the selected rows with enumerator
while ( ( index = [e nextObject] ) )
{
// Retrieve the selected records
if ([index intValue]<= [activeSet count] - 1)
{
realIndex = [records indexOfObjectIdenticalTo: [activeSet objectAtIndex: [index intValue]]];


// Store the object into the temporary array
[tempArray addObject: [records objectAtIndex: realIndex]];
}
}


return tempArray;
}


- (void) synchronizeRecord: (NSString *) aString
{
NSEnumerator *e = [records objectEnumerator];
NSString *fnString, *lnString;
id object;


if ([aString length] == 0)
{
activeSet = records;
return;
}


[subset release];
subset = [[NSMutableArray alloc] init];


while (object = [e nextObject])
{
fnString = [[object objectForKey: @"First Name"] lowercaseString];
lnString = [[object objectForKey: @"Last Name"] lowercaseString];


if ([fnString hasPrefix: aString] || [lnString hasPrefix: aString])
{
[subset addObject: object];
}
}


activeSet = subset;
}


- (void) synchronizeSelection: (NSMutableArray *) anArray
{
NSEnumerator *e;
id anObject;

[tableView reloadData];


// Enumerate the stored objects
e = [anArray objectEnumerator];


// Deselect all rows
[tableView deselectAll: self];


// Reselect the previously selected rows
while (anObject = [e nextObject])
{
// ... if they exist in the current subset
if ([activeSet indexOfObjectIdenticalTo: anObject] != NSNotFound)
{
[tableView selectRow: [activeSet indexOfObjectIdenticalTo: anObject] byExtendingSelection: YES];
}
}
}


Besides of things already mentioned, It remains to have the insert and delete controls disabled when no row is selected, and to dim the corresponding text or image in toolbar accordingly. The first one I'll guess I could do it by registering the controller with selectionDidChangeNotification, the second one I still have no idea how to do it.


Any comment to this soap would be greatly appreciated.


Michele