Annotation of Examples/EnterpriseObjects/QueryByExample/QBEPalette/QBE.m, revision 1.1.1.1

1.1       root        1: /* QBE.m:
                      2:  * You may freely copy, distribute, and reuse the code in this example.
                      3:  * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
                      4:  * fitness for any particular use.
                      5:  *
                      6:  * Written by Craig Federighi
                      7:  *  
                      8:  *
                      9:  * Example of a Query By Example object: Connects to an EOController for an
                     10:  * easy contruction of qualifiers from data entered into fields of an 
                     11:  * existing user interface.
                     12:  */
                     13:  
                     14: #import "QBE.h"
                     15: #import "DictionaryDataSource.h"
                     16: 
                     17: #import <eoaccess/eoaccess.h>
                     18: #import <foundation/NSString.h>
                     19: #import <foundation/NSArray.h>
                     20: 
                     21: #import <foundation/NSCharacterSet.h>
                     22: #import <foundation/NSScanner.h>
                     23: 
                     24: 
                     25: 
                     26: /* A private category to strip white spaces */
                     27:  
                     28: @interface NSString(_QBEStripWhite) 
                     29: - (NSString *)stringStrippingLeadingCharacterSet:(NSCharacterSet *)skipset;
                     30: @end
                     31: 
                     32: @implementation NSString(_QBEStripWhite) 
                     33: 
                     34: /* Returns autoreleased string produced by generating new string
                     35:  * with the leading junk skipped.
                     36:  */
                     37: - (NSString *)stringStrippingLeadingCharacterSet:(NSCharacterSet *)skipset
                     38:   {
                     39:     NSRange range;
                     40:     
                     41:     range = [self rangeOfCharacterFromSet: [skipset invertedSet]];
                     42:     return [self substringFromIndex: range.location];
                     43: }
                     44: 
                     45: @end
                     46: 
                     47: /*  A category on EOAssociation to turn on and off editability of
                     48:  *  underlying UI objects
                     49:  */
                     50: @interface EOAssociation(_QBEEditable)
                     51: - (BOOL)isEditable;
                     52: - (void)setEditable:(BOOL)yn;
                     53: @end
                     54: 
                     55: @implementation EOAssociation(_QBEEditable)
                     56: - (BOOL)isEditable { return NO; }
                     57: - (void)setEditable:(BOOL)yn {}
                     58: @end
                     59: 
                     60: @implementation EOControlAssociation(_QBEEditable)
                     61: - (BOOL)isEditable {
                     62:     Cell * cell;
                     63:     if ([[self destination] isKindOfClass:[ActionCell class]])
                     64:         cell = [self destination];
                     65:     else 
                     66:         cell = [[self destination] cell];
                     67:        
                     68:     return [cell isEditable]; 
                     69: }
                     70: 
                     71: - (void)setEditable:(BOOL)yn {
                     72:     Cell * cell;
                     73:     if ([[self destination] isKindOfClass:[ActionCell class]])
                     74:         cell = [self destination];
                     75:     else 
                     76:         cell = [[self destination] cell];
                     77: 
                     78:     [cell setEditable:yn]; 
                     79: }
                     80: @end
                     81: 
                     82: @implementation EOColumnAssociation(_QBEEditable)
                     83: - (BOOL)isEditable {return [tableView isEditable]; }
                     84: - (void)setEditable:(BOOL)yn 
                     85: { 
                     86:     [tableView setEditable:yn]; 
                     87: }
                     88: @end
                     89: 
                     90: 
                     91: @implementation EOActionCellAssociation(_QBEEditable)
                     92: - (BOOL)isEditable { 
                     93:     return [(Cell *)[self destination] isEditable]; 
                     94: }
                     95: 
                     96: - (void)setEditable:(BOOL)yn {
                     97:     [(Cell *)[self destination] setEditable:yn]; 
                     98: }
                     99: @end
                    100: 
                    101: 
                    102: /* Need to tell what sort of associations can participate in QBE
                    103:  * editing.  Master-detail associations, for example, should be 
                    104:  * deactivated.
                    105:  */
                    106: @interface EOAssociation(_isQBEEditor)
                    107: - (BOOL)isQBEEditor; // Should remain active in QBE editing
                    108: @end
                    109: 
                    110: @implementation EOAssociation(_isQBEEditor)
                    111: - (BOOL)isQBEEditor  {return YES;}
                    112: @end
                    113: 
                    114: @implementation EOQualifiedAssociation(_isQBEEditor)
                    115: - (BOOL)isQBEEditor  {return NO;}
                    116: @end
                    117: 
                    118: // Make sure an Adaptor is ready to convert values
                    119: // (the SybaseAdaptor can't do this until after it's connected)
                    120: @interface EOAdaptorChannel (canConvert)
                    121: - (BOOL)canConvertValues;
                    122: @end
                    123: 
                    124: @implementation EOAdaptorChannel (canConvert)
                    125: - (BOOL)canConvertValues
                    126: {
                    127:     EOAdaptorChannel *adaptorChannel = nil;
                    128:     EOAdaptor *adaptor;
                    129:     BOOL retValue = YES;
                    130: 
                    131:     if (![self isOpen]) {
                    132:         adaptor = [[adaptorChannel adaptorContext] adaptor];
                    133:         if (![adaptor hasValidConnectionDictionary]){
                    134:             if (![adaptor runLoginPanelAndValidateConnectionDictionary])
                    135:                 retValue = NO;
                    136:         }
                    137: 
                    138:         if (retValue == YES && ![self openChannel])
                    139:             retValue = NO;
                    140:     }
                    141:     return retValue;
                    142: }
                    143: @end
                    144: 
                    145: 
                    146: 
                    147: /* Actual implementation of QBE object */
                    148: 
                    149: @implementation QBE
                    150: 
                    151: - init
                    152: {
                    153:     return self;
                    154: }
                    155: 
                    156: static NSString *operators[] = {
                    157:     @"=",
                    158:     @">",
                    159:     @"<",
                    160:     @"<=",
                    161:     @">=",
                    162:     NULL
                    163: };
                    164: 
                    165: 
                    166: /* Build the qualifier based on the user input */
                    167: 
                    168: - qualifierForKey:(NSString *)key value:value entity:(EOEntity *)entity
                    169: {
                    170:     EOAdaptorChannel *adaptorChannel = [[realsource databaseChannel] adaptorChannel];
                    171:     EOAdaptor *adaptor = [[adaptorChannel adaptorContext] adaptor];
                    172:     NSString **ops;
                    173:     NSString *op = nil, *fmt;
                    174:     id target = value;
                    175:     EOQualifier *qualifier;
                    176:     EOAttribute *attr;
                    177:     BOOL isStringAttribute;
                    178:     
                    179:     // Be sure that we can connect to the database before constructing a
                    180:     // qualifier.  Without a connection, the adaptor can't
                    181:     // formatValue:forAttribute:
                    182:     if (![adaptorChannel canConvertValues])
                    183:         return nil;
                    184:     
                    185:     [adaptorChannel setDebugEnabled:YES];
                    186:     
                    187:     // Check type of attribute so we know whether to quote target
                    188:     attr = [entity attributeNamed:key];
                    189:     isStringAttribute = (strcmp([attr valueClassName], "NSString") == 0);
                    190:     
                    191:     // Look for an operator.  If we see one, rip it off and use it    
                    192:     if ([value isKindOfClass:[NSString class]]) {
                    193:         for(ops=operators; *ops; ops++) {
                    194:            if ( [(NSString *)value hasPrefix: *ops] ) {
                    195:                op = *ops;
                    196:                
                    197:                // Need to strip whitespace!
                    198:                target = [[(NSString *)value substringFromIndex: [op length]]
                    199:                                stringStrippingLeadingCharacterSet: 
                    200:                                   [NSCharacterSet whitespaceCharacterSet]];
                    201:                break;
                    202:            }
                    203:        }
                    204:     }
                    205: 
                    206:     // compute format string
                    207:     if (isStringAttribute && !op) {
                    208:        // Wildcard search
                    209:        op = @"LIKE";
                    210:        target = [NSString stringWithFormat:@"%%%@%%", target];
                    211:     } else if (!op) {
                    212:        // Default for non string attributes
                    213:        op = @"=";
                    214:     }
                    215: 
                    216:     fmt = @"%A %@ %@";
                    217:     qualifier = [[EOQualifier alloc] initWithEntity:entity 
                    218:            qualifierFormat:fmt, key, op,
                    219:            [adaptor formatValue:target forAttribute:attr]];
                    220:     return [qualifier autorelease];
                    221: }
                    222: 
                    223: 
                    224: - (EOQualifier *)makeQualifierForEo:eo entity:(EOEntity *)entity 
                    225:                                    conjoin:(BOOL)isConjoin
                    226:   // Construct a qualifier for an EO
                    227: {
                    228:     NSDictionary *valueDict;
                    229:     NSArray *attributes;
                    230:     NSMutableArray *attrnames;
                    231:     int count, i;
                    232:     EOQualifier *qualifier, *root = nil;
                    233:     
                    234:     // get a dictionary of values for the eo
                    235:     attributes = [entity attributes];
                    236: 
                    237:     count = [attributes count];
                    238:     attrnames = [NSMutableArray arrayWithCapacity:count];
                    239:     
                    240:     // We should really only ask for the classAttributes
                    241:     for(i=0; i<count; i++)
                    242:        [attrnames addObject:
                    243:            [(EOAttribute *)[attributes objectAtIndex:i] name]];
                    244: 
                    245:     valueDict = [eo valuesForKeys:attrnames];   
                    246: 
                    247:     for(i=0; i<count; i++) {
                    248:         NSString *key = [attrnames objectAtIndex:i];
                    249:        id value = [valueDict objectForKey:key];
                    250:        if( value ) {
                    251:            qualifier = [self qualifierForKey:key value:value entity:entity];
                    252:            if (qualifier) {
                    253:                if (root) {
                    254:                    if (isConjoin)
                    255:                        [root conjoinWithQualifier:qualifier];
                    256:                    else
                    257:                        [root disjoinWithQualifier:qualifier];
                    258:                } else 
                    259:                    root = qualifier;
                    260:            }
                    261:        }                       
                    262:     }
                    263:     
                    264:     return root;
                    265: }
                    266: 
                    267: - makeQualifier:(BOOL)isConjoin
                    268: {
                    269:     EOEntity *entity;
                    270:     NSArray *objects;
                    271:     EOQualifier *root = nil;
                    272:     int count, i;
                    273:     
                    274:     // force flush of changes to objects
                    275:     [controller saveToObjects];
                    276:     objects = [controller allObjects];    
                    277:     entity = [realsource entity];
                    278: 
                    279:     for(count=[objects count],i=0; i < count; i++) {
                    280:         id eo = [objects objectAtIndex:i];
                    281:        EOQualifier *q = [self makeQualifierForEo:eo entity:entity
                    282:                                    conjoin:isConjoin];
                    283:         if (root && q)
                    284:            [root disjoinWithQualifier:q];
                    285:        else 
                    286:            root = q;
                    287:     } 
                    288: 
                    289:     return root;
                    290: }
                    291: 
                    292: - enterQueryMode:sender
                    293: // tweak the controller into QBE state:
                    294: //  - no records, buffer edits, save to objects
                    295: {
                    296:     DictionaryDataSource *dictsource;
                    297:     NSArray *associations;
                    298:     int i, count;
                    299:     EOEntity *entity;
                    300:     
                    301:     if (realsource) 
                    302:         return [self addQuery:nil];
                    303:     
                    304:     // flush everything we have now
                    305:     [controller saveToDataSource];
                    306: 
                    307:     // swap in tempory datasource
                    308:     realsource = [(EODatabaseDataSource *)[controller dataSource] retain];
                    309:     dictsource = [[[DictionaryDataSource alloc] init] autorelease];
                    310:     [controller setDataSource: dictsource];
                    311:     
                    312:     // We don't want any detail controllers to be updated to match the
                    313:     // QBE entry (since it's meaningless and probably not complete)
                    314:     
                    315:     // Prepare array to add associations to
                    316:     removedAssociations = [[NSMutableArray alloc] init];
                    317:     
                    318:     // Remove all Qualified Associations
                    319:     associations = [controller associations];
                    320:     for(count=[associations count], i=0; i < count; i++) {
                    321:         EOAssociation *assoc = [associations objectAtIndex: i];
                    322:        if (![assoc isQBEEditor]) {
                    323:            [removedAssociations addObject: assoc];
                    324:            [controller removeAssociation:assoc];
                    325:        }
                    326:     }
                    327:     
                    328:     // Remove the next controller (for a fetch)
                    329:     nextController = [controller nextController];
                    330:     [controller setNextController: nil];
                    331:     
                    332:     // unset the delegate
                    333:     controllersDelegate = [controller delegate];
                    334:     [controller setDelegate:nil];
                    335: 
                    336:     // Should force all "qualifyable" associations (to database attributes) 
                    337:     // to editable and all others uneditable.
                    338: 
                    339:     // Make all remaining associations editable
                    340:     // Remember those that weren't so we can restore them.
                    341:     entity = [realsource entity];
                    342:     wereUneditableAssoc = [[NSMutableArray alloc] init];
                    343:     wereEditableAssoc = [[NSMutableArray alloc] init];
                    344:     associations = [controller associations];
                    345: 
                    346:     // Note which are uneditable
                    347:     for(count=[associations count], i=0; i < count; i++) {
                    348:         EOAssociation *assoc = [associations objectAtIndex: i];
                    349:        BOOL isQualifyable = ([entity attributeNamed:[assoc key]] != nil);
                    350:        if ( isQualifyable && ![assoc isEditable] ) {
                    351:            [wereUneditableAssoc addObject:assoc];
                    352:        } else if (!isQualifyable && [assoc isEditable]) {
                    353:            [wereUneditableAssoc addObject:assoc];
                    354:        }
                    355:     }  
                    356:     // make them temporily editable
                    357:     for(count=[wereUneditableAssoc count], i=0; i < count; i++) {
                    358:         EOAssociation *assoc = [wereUneditableAssoc objectAtIndex: i];
                    359:        [assoc setEditable:YES];
                    360:     }  
                    361:     // make them temporily uneditable
                    362:     for(count=[wereEditableAssoc count], i=0; i < count; i++) {
                    363:         EOAssociation *assoc = [wereEditableAssoc objectAtIndex: i];
                    364:        [assoc setEditable:NO];
                    365:     }  
                    366: 
                    367:     return [self addQuery:nil];
                    368: }
                    369: 
                    370: - addQuery:sender
                    371: {
                    372:     int count;
                    373:     if (!realsource) return nil;
                    374: 
                    375:     count = [[controller allObjects] count];
                    376:     
                    377:     [controller insertObjectAtIndex:count];
                    378:     [controller setSelectionIndexes:
                    379:            [NSArray arrayWithObject:[NSNumber numberWithInt:count]]];
                    380: 
                    381:     return self;
                    382: }
                    383: 
                    384: - exitQueryMode:sender
                    385:     // restore old datasource and controller settings
                    386: {
                    387:     int count, i;
                    388:     
                    389:     // restore datasource
                    390:     [controller setDataSource: realsource]; 
                    391:     realsource = nil; // mark that we're out of query mode
                    392:     
                    393:     // restore removed qualified associations
                    394:     for(count=[removedAssociations count], i=0; i<count; i++) {
                    395:         EOAssociation *assoc = [removedAssociations objectAtIndex:i];
                    396:         [controller addAssociation:assoc];
                    397:     }
                    398:     [removedAssociations release];
                    399: 
                    400:     // Reset uneditable associations to back to uneditable again
                    401:     for(count=[wereUneditableAssoc count], i=0; i<count; i++) {
                    402:         EOAssociation *assoc = [wereUneditableAssoc objectAtIndex:i];
                    403:         [assoc setEditable:NO];
                    404:     }
                    405:     [wereUneditableAssoc release];
                    406:     
                    407:     // Reset editable associations to back to editable again
                    408:     for(count=[wereEditableAssoc count], i=0; i<count; i++) {
                    409:         EOAssociation *assoc = [wereEditableAssoc objectAtIndex:i];
                    410:         [assoc setEditable:YES];
                    411:     }
                    412:     [wereEditableAssoc release];
                    413:     
                    414:     
                    415:     // restore next controller
                    416:     [controller setNextController:nextController];
                    417:     
                    418:     // restore the delegate
                    419:     [controller setDelegate:controllersDelegate];
                    420:     
                    421:     [controller fetch];    
                    422: 
                    423:     return self;
                    424: }
                    425: 
                    426: - actionOutsideQueryMode
                    427: {
                    428:     // We're not in query mode and they pushed a query button.
                    429:     // Reset the qualifier and do a basic fetch
                    430:     EODatabaseDataSource *source = (EODatabaseDataSource *)[controller dataSource];
                    431:     [source setAuxiliaryQualifier:nil];
                    432:     [controller fetch];
                    433:     return self;
                    434: }
                    435: 
                    436: - applyQualifier:sender
                    437: // set qualifier to AND query for current attr settings, 
                    438: // exit query mode and fetch
                    439: {
                    440:     if (!realsource) 
                    441:        return [self actionOutsideQueryMode];
                    442:     
                    443:     [realsource setAuxiliaryQualifier:[self makeQualifier:YES]];  // conjoin
                    444:     return [self exitQueryMode:self];
                    445: }
                    446: 
                    447: - applyDisjointQualifier:sender
                    448: // set qualifier to AND query for current attr settings, 
                    449: // exit query mode and fetch
                    450: {
                    451:     if (!realsource) 
                    452:        return [self actionOutsideQueryMode];
                    453:     
                    454:     [realsource setAuxiliaryQualifier:[self makeQualifier:NO]];  // Disjoin
                    455:     return [self exitQueryMode:self];
                    456: }
                    457: 
                    458: - toggleQueryFetch:sender
                    459: {
                    460:     return (realsource) ? [self applyQualifier:sender] 
                    461:                             : [self enterQueryMode:sender];
                    462: }
                    463: 
                    464: @end
                    465: 

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.