Annotation of Examples/EnterpriseObjects/QueryByExample/QBEPalette/QBE.m, revision 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.