|
|
Sample Programs from NeXSTEP 3.3
/* QBE.m:
* You may freely copy, distribute, and reuse the code in this example.
* NeXT disclaims any warranty of any kind, expressed or implied, as to its
* fitness for any particular use.
*
* Written by Craig Federighi
*
*
* Example of a Query By Example object: Connects to an EOController for an
* easy contruction of qualifiers from data entered into fields of an
* existing user interface.
*/
#import "QBE.h"
#import "DictionaryDataSource.h"
#import <eoaccess/eoaccess.h>
#import <foundation/NSString.h>
#import <foundation/NSArray.h>
#import <foundation/NSCharacterSet.h>
#import <foundation/NSScanner.h>
/* A private category to strip white spaces */
@interface NSString(_QBEStripWhite)
- (NSString *)stringStrippingLeadingCharacterSet:(NSCharacterSet *)skipset;
@end
@implementation NSString(_QBEStripWhite)
/* Returns autoreleased string produced by generating new string
* with the leading junk skipped.
*/
- (NSString *)stringStrippingLeadingCharacterSet:(NSCharacterSet *)skipset
{
NSRange range;
range = [self rangeOfCharacterFromSet: [skipset invertedSet]];
return [self substringFromIndex: range.location];
}
@end
/* A category on EOAssociation to turn on and off editability of
* underlying UI objects
*/
@interface EOAssociation(_QBEEditable)
- (BOOL)isEditable;
- (void)setEditable:(BOOL)yn;
@end
@implementation EOAssociation(_QBEEditable)
- (BOOL)isEditable { return NO; }
- (void)setEditable:(BOOL)yn {}
@end
@implementation EOControlAssociation(_QBEEditable)
- (BOOL)isEditable {
Cell * cell;
if ([[self destination] isKindOfClass:[ActionCell class]])
cell = [self destination];
else
cell = [[self destination] cell];
return [cell isEditable];
}
- (void)setEditable:(BOOL)yn {
Cell * cell;
if ([[self destination] isKindOfClass:[ActionCell class]])
cell = [self destination];
else
cell = [[self destination] cell];
[cell setEditable:yn];
}
@end
@implementation EOColumnAssociation(_QBEEditable)
- (BOOL)isEditable {return [tableView isEditable]; }
- (void)setEditable:(BOOL)yn
{
[tableView setEditable:yn];
}
@end
@implementation EOActionCellAssociation(_QBEEditable)
- (BOOL)isEditable {
return [(Cell *)[self destination] isEditable];
}
- (void)setEditable:(BOOL)yn {
[(Cell *)[self destination] setEditable:yn];
}
@end
/* Need to tell what sort of associations can participate in QBE
* editing. Master-detail associations, for example, should be
* deactivated.
*/
@interface EOAssociation(_isQBEEditor)
- (BOOL)isQBEEditor; // Should remain active in QBE editing
@end
@implementation EOAssociation(_isQBEEditor)
- (BOOL)isQBEEditor {return YES;}
@end
@implementation EOQualifiedAssociation(_isQBEEditor)
- (BOOL)isQBEEditor {return NO;}
@end
// Make sure an Adaptor is ready to convert values
// (the SybaseAdaptor can't do this until after it's connected)
@interface EOAdaptorChannel (canConvert)
- (BOOL)canConvertValues;
@end
@implementation EOAdaptorChannel (canConvert)
- (BOOL)canConvertValues
{
EOAdaptorChannel *adaptorChannel = nil;
EOAdaptor *adaptor;
BOOL retValue = YES;
if (![self isOpen]) {
adaptor = [[adaptorChannel adaptorContext] adaptor];
if (![adaptor hasValidConnectionDictionary]){
if (![adaptor runLoginPanelAndValidateConnectionDictionary])
retValue = NO;
}
if (retValue == YES && ![self openChannel])
retValue = NO;
}
return retValue;
}
@end
/* Actual implementation of QBE object */
@implementation QBE
- init
{
return self;
}
static NSString *operators[] = {
@"=",
@">",
@"<",
@"<=",
@">=",
NULL
};
/* Build the qualifier based on the user input */
- qualifierForKey:(NSString *)key value:value entity:(EOEntity *)entity
{
EOAdaptorChannel *adaptorChannel = [[realsource databaseChannel] adaptorChannel];
EOAdaptor *adaptor = [[adaptorChannel adaptorContext] adaptor];
NSString **ops;
NSString *op = nil, *fmt;
id target = value;
EOQualifier *qualifier;
EOAttribute *attr;
BOOL isStringAttribute;
// Be sure that we can connect to the database before constructing a
// qualifier. Without a connection, the adaptor can't
// formatValue:forAttribute:
if (![adaptorChannel canConvertValues])
return nil;
[adaptorChannel setDebugEnabled:YES];
// Check type of attribute so we know whether to quote target
attr = [entity attributeNamed:key];
isStringAttribute = (strcmp([attr valueClassName], "NSString") == 0);
// Look for an operator. If we see one, rip it off and use it
if ([value isKindOfClass:[NSString class]]) {
for(ops=operators; *ops; ops++) {
if ( [(NSString *)value hasPrefix: *ops] ) {
op = *ops;
// Need to strip whitespace!
target = [[(NSString *)value substringFromIndex: [op length]]
stringStrippingLeadingCharacterSet:
[NSCharacterSet whitespaceCharacterSet]];
break;
}
}
}
// compute format string
if (isStringAttribute && !op) {
// Wildcard search
op = @"LIKE";
target = [NSString stringWithFormat:@"%%%@%%", target];
} else if (!op) {
// Default for non string attributes
op = @"=";
}
fmt = @"%A %@ %@";
qualifier = [[EOQualifier alloc] initWithEntity:entity
qualifierFormat:fmt, key, op,
[adaptor formatValue:target forAttribute:attr]];
return [qualifier autorelease];
}
- (EOQualifier *)makeQualifierForEo:eo entity:(EOEntity *)entity
conjoin:(BOOL)isConjoin
// Construct a qualifier for an EO
{
NSDictionary *valueDict;
NSArray *attributes;
NSMutableArray *attrnames;
int count, i;
EOQualifier *qualifier, *root = nil;
// get a dictionary of values for the eo
attributes = [entity attributes];
count = [attributes count];
attrnames = [NSMutableArray arrayWithCapacity:count];
// We should really only ask for the classAttributes
for(i=0; i<count; i++)
[attrnames addObject:
[(EOAttribute *)[attributes objectAtIndex:i] name]];
valueDict = [eo valuesForKeys:attrnames];
for(i=0; i<count; i++) {
NSString *key = [attrnames objectAtIndex:i];
id value = [valueDict objectForKey:key];
if( value ) {
qualifier = [self qualifierForKey:key value:value entity:entity];
if (qualifier) {
if (root) {
if (isConjoin)
[root conjoinWithQualifier:qualifier];
else
[root disjoinWithQualifier:qualifier];
} else
root = qualifier;
}
}
}
return root;
}
- makeQualifier:(BOOL)isConjoin
{
EOEntity *entity;
NSArray *objects;
EOQualifier *root = nil;
int count, i;
// force flush of changes to objects
[controller saveToObjects];
objects = [controller allObjects];
entity = [realsource entity];
for(count=[objects count],i=0; i < count; i++) {
id eo = [objects objectAtIndex:i];
EOQualifier *q = [self makeQualifierForEo:eo entity:entity
conjoin:isConjoin];
if (root && q)
[root disjoinWithQualifier:q];
else
root = q;
}
return root;
}
- enterQueryMode:sender
// tweak the controller into QBE state:
// - no records, buffer edits, save to objects
{
DictionaryDataSource *dictsource;
NSArray *associations;
int i, count;
EOEntity *entity;
if (realsource)
return [self addQuery:nil];
// flush everything we have now
[controller saveToDataSource];
// swap in tempory datasource
realsource = [(EODatabaseDataSource *)[controller dataSource] retain];
dictsource = [[[DictionaryDataSource alloc] init] autorelease];
[controller setDataSource: dictsource];
// We don't want any detail controllers to be updated to match the
// QBE entry (since it's meaningless and probably not complete)
// Prepare array to add associations to
removedAssociations = [[NSMutableArray alloc] init];
// Remove all Qualified Associations
associations = [controller associations];
for(count=[associations count], i=0; i < count; i++) {
EOAssociation *assoc = [associations objectAtIndex: i];
if (![assoc isQBEEditor]) {
[removedAssociations addObject: assoc];
[controller removeAssociation:assoc];
}
}
// Remove the next controller (for a fetch)
nextController = [controller nextController];
[controller setNextController: nil];
// unset the delegate
controllersDelegate = [controller delegate];
[controller setDelegate:nil];
// Should force all "qualifyable" associations (to database attributes)
// to editable and all others uneditable.
// Make all remaining associations editable
// Remember those that weren't so we can restore them.
entity = [realsource entity];
wereUneditableAssoc = [[NSMutableArray alloc] init];
wereEditableAssoc = [[NSMutableArray alloc] init];
associations = [controller associations];
// Note which are uneditable
for(count=[associations count], i=0; i < count; i++) {
EOAssociation *assoc = [associations objectAtIndex: i];
BOOL isQualifyable = ([entity attributeNamed:[assoc key]] != nil);
if ( isQualifyable && ![assoc isEditable] ) {
[wereUneditableAssoc addObject:assoc];
} else if (!isQualifyable && [assoc isEditable]) {
[wereUneditableAssoc addObject:assoc];
}
}
// make them temporily editable
for(count=[wereUneditableAssoc count], i=0; i < count; i++) {
EOAssociation *assoc = [wereUneditableAssoc objectAtIndex: i];
[assoc setEditable:YES];
}
// make them temporily uneditable
for(count=[wereEditableAssoc count], i=0; i < count; i++) {
EOAssociation *assoc = [wereEditableAssoc objectAtIndex: i];
[assoc setEditable:NO];
}
return [self addQuery:nil];
}
- addQuery:sender
{
int count;
if (!realsource) return nil;
count = [[controller allObjects] count];
[controller insertObjectAtIndex:count];
[controller setSelectionIndexes:
[NSArray arrayWithObject:[NSNumber numberWithInt:count]]];
return self;
}
- exitQueryMode:sender
// restore old datasource and controller settings
{
int count, i;
// restore datasource
[controller setDataSource: realsource];
realsource = nil; // mark that we're out of query mode
// restore removed qualified associations
for(count=[removedAssociations count], i=0; i<count; i++) {
EOAssociation *assoc = [removedAssociations objectAtIndex:i];
[controller addAssociation:assoc];
}
[removedAssociations release];
// Reset uneditable associations to back to uneditable again
for(count=[wereUneditableAssoc count], i=0; i<count; i++) {
EOAssociation *assoc = [wereUneditableAssoc objectAtIndex:i];
[assoc setEditable:NO];
}
[wereUneditableAssoc release];
// Reset editable associations to back to editable again
for(count=[wereEditableAssoc count], i=0; i<count; i++) {
EOAssociation *assoc = [wereEditableAssoc objectAtIndex:i];
[assoc setEditable:YES];
}
[wereEditableAssoc release];
// restore next controller
[controller setNextController:nextController];
// restore the delegate
[controller setDelegate:controllersDelegate];
[controller fetch];
return self;
}
- actionOutsideQueryMode
{
// We're not in query mode and they pushed a query button.
// Reset the qualifier and do a basic fetch
EODatabaseDataSource *source = (EODatabaseDataSource *)[controller dataSource];
[source setAuxiliaryQualifier:nil];
[controller fetch];
return self;
}
- applyQualifier:sender
// set qualifier to AND query for current attr settings,
// exit query mode and fetch
{
if (!realsource)
return [self actionOutsideQueryMode];
[realsource setAuxiliaryQualifier:[self makeQualifier:YES]]; // conjoin
return [self exitQueryMode:self];
}
- applyDisjointQualifier:sender
// set qualifier to AND query for current attr settings,
// exit query mode and fetch
{
if (!realsource)
return [self actionOutsideQueryMode];
[realsource setAuxiliaryQualifier:[self makeQualifier:NO]]; // Disjoin
return [self exitQueryMode:self];
}
- toggleQueryFetch:sender
{
return (realsource) ? [self applyQualifier:sender]
: [self enterQueryMode:sender];
}
@end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.