Annotation of Examples/AppKit/Draw/Image.m, revision 1.1.1.1

1.1       root        1: #import "draw.h"
                      2: 
                      3: /* Optimally viewed in a wide window.  Make your window big enough so that this comment fits on one line without wrapping. */
                      4: 
                      5: /*
                      6:  * Image is a simple graphic which takes PostScript or
                      7:  * TIFF images and draws them in a bounding box (it scales
                      8:  * the image if the bounding box is changed).  It is
                      9:  * implemented using the NXImage class.  Using NXImage
                     10:  * here is especially nice since it images its PostScript
                     11:  * in a separate context (thus, any errors that PostScript
                     12:  * generates will not affect our main drawing context).
                     13:  */
                     14: 
                     15: @implementation Image : Graphic
                     16: 
                     17: /* Initialize the class */
                     18: 
                     19: + initialize
                     20: {
                     21:     [Image setVersion:7];
                     22:     return self;
                     23: }
                     24: 
                     25: /* Factory methods. */
                     26: 
                     27: + highlightedLinkButtonImage:(NXSize *)size
                     28: /*
                     29:  * Just makes an NXLinkButtonH NXImage the same size as
                     30:  * the size passed in.  I suppose this could just be a
                     31:  * function.
                     32:  */
                     33: {
                     34:     static NXImage *retval = nil;
                     35:     if (!retval) {
                     36:        retval = [[NXImage findImageNamed:"NXLinkButtonH"] copy];
                     37:        [retval setScalable:YES];
                     38:        [retval setDataRetained:YES];
                     39:     }
                     40:     [retval setSize:size];
                     41:     return retval;
                     42: }
                     43: 
                     44: + (BOOL)canInitFromPasteboard:(Pasteboard *)pboard
                     45: {
                     46:     return [NXImage canInitFromPasteboard:pboard];
                     47: }
                     48: 
                     49: static BOOL checkImage(NXImage *anImage)
                     50: /*
                     51:  * Locking focus on an NXImage forces it to draw and thus verifies
                     52:  * whether there are any PostScript or TIFF errors in the source of
                     53:  * the image.  lockFocus returns YES only if there are no errors.
                     54:  */
                     55: {
                     56:     if ([anImage lockFocus]) {
                     57:        [anImage unlockFocus];
                     58:        return YES;
                     59:     }
                     60:     return NO;
                     61: }
                     62: 
                     63: /* Creation/Initialization Methods */
                     64: 
                     65: - init
                     66: /*
                     67:  * This creates basically an "empty" Image.
                     68:  * This is the designated initializer for Image.
                     69:  * Be careful, however, because by the time this
                     70:  * returns, a newly initialized Image may not be
                     71:  * fully initialized (it'll be "valid," just not
                     72:  * necessarily fully initialized).  If you want that
                     73:  * behaviour, override finishedWithInit.
                     74:  */
                     75: {
                     76:     [super init];
                     77:     originalSize.width = originalSize.height = 1.0;
                     78:     bounds.size = originalSize;
                     79:     return self;
                     80: }
                     81: 
                     82: - finishedWithInit
                     83: /*
                     84:  * Called when a newly initialized Image is fully
                     85:  * initialized and ready to roll.  For subclassers
                     86:  * only.
                     87:  */
                     88: {
                     89:     return self;
                     90: }
                     91: 
                     92: - initEmpty
                     93: /*
                     94:  * Creates a blank Image.
                     95:  */
                     96: {
                     97:     [self init];
                     98:     return [self finishedWithInit];
                     99: }
                    100: 
                    101: - initFromStream:(NXStream *)stream
                    102: /*
                    103:  * Creates a new NXImage and sets it to be scalable and to retain
                    104:  * its data (which means that when we archive it, it will actually
                    105:  * write the TIFF or PostScript data into the stream).
                    106:  */
                    107: {
                    108:     [self init];
                    109: 
                    110:     if (stream) {
                    111:        image = [NXImage allocFromZone:[self zone]];
                    112:        if ((image = [image initFromStream:stream])) {
                    113:            [image setDataRetained:YES];
                    114:            if (checkImage(image)) {
                    115:                [image getSize:&originalSize];
                    116:                [image setScalable:YES];
                    117:                bounds.size = originalSize;
                    118:                return [self finishedWithInit];
                    119:            }
                    120:        }
                    121:     }
                    122: 
                    123:     [self free];
                    124: 
                    125:     return nil;
                    126: }
                    127: 
                    128: - initFromPasteboard:(Pasteboard *)pboard;
                    129: /*
                    130:  * Creates a new NXImage and sets it to be scalable and to retain
                    131:  * its data (which means that when we archive it, it will actually
                    132:  * write the TIFF or PostScript data into the stream).
                    133:  */
                    134: {
                    135:     [self init];
                    136: 
                    137:     if (pboard) {
                    138:        image = [NXImage allocFromZone:[self zone]];
                    139:        if ((image = [image initFromPasteboard:pboard])) {
                    140:            [image setDataRetained:YES];
                    141:            if (checkImage(image)) {
                    142:                [image getSize:&originalSize];
                    143:                [image setScalable:YES];
                    144:                bounds.size = originalSize;
                    145:                return [self finishedWithInit];
                    146:            }
                    147:        }
                    148:     }
                    149: 
                    150:     [self free];
                    151: 
                    152:     return nil;
                    153: }
                    154: 
                    155: - initFromFile:(const char *)file
                    156: /*
                    157:  * Creates an NXImage by reading data from an .eps or .tiff file.
                    158:  */
                    159: {
                    160:     [self init];
                    161: 
                    162:     image = [[NXImage allocFromZone:[self zone]] init];
                    163:     if ([image loadFromFile:file]) {
                    164:        [image setDataRetained:YES];
                    165:        if (checkImage(image)) {
                    166:            [image getSize:&originalSize];
                    167:            [image setScalable:YES];
                    168:            bounds.size = originalSize;
                    169:            return [self finishedWithInit];
                    170:        }
                    171:     }
                    172: 
                    173:     [self free];
                    174: 
                    175:     return nil;
                    176: }
                    177: 
                    178: - doInitFromImage:(NXImage *)anImage
                    179: /*
                    180:  * Common code for initFromImage: and unarchiving.
                    181:  */
                    182: {
                    183:     if (anImage) {
                    184:        image = anImage;
                    185:        [image getSize:&originalSize];
                    186:        [image setScalable:YES];
                    187:        [image setDataRetained:YES];
                    188:        bounds.size = originalSize;
                    189:     } else {
                    190:        [self free];
                    191:        self = nil;
                    192:     }
                    193:     return self;
                    194: }
                    195: 
                    196: - initFromImage:(NXImage *)anImage
                    197: /*
                    198:  * Initializes an Image from a specific NXImage.
                    199:  */
                    200: {
                    201:     [self init];
                    202:     return [[self doInitFromImage:anImage] finishedWithInit];
                    203: }
                    204: 
                    205: - initFromIcon:(NXImage *)anImage
                    206: /*
                    207:  * Same as initFromImage:, but we remember that this particular
                    208:  * NXImage was actually a file icon (which enables us to double-click
                    209:  * on it to open the icon, see handleEvent:).
                    210:  */
                    211: {
                    212:     if ([self initFromImage:anImage]) {
                    213:        amIcon = YES;
                    214:        return self;
                    215:     } else {
                    216:        return nil;
                    217:     }
                    218: }
                    219: 
                    220: - initWithLinkButton
                    221: /*
                    222:  * Creates an image which is just the link button.
                    223:  * This is only applicable with Object Links.
                    224:  */
                    225: {
                    226:     if ([self initFromImage:[[NXImage findImageNamed:"NXLinkButton"] copy]]) {
                    227:        amLinkButton = YES;
                    228:        return self;
                    229:     } else {
                    230:        return nil;
                    231:     }
                    232: }
                    233: 
                    234: - (NXRect)resetImage:(NXImage *)newImage
                    235: /*
                    236:  * Called by the "reinit" methods to reset all of our instance
                    237:  * variables based on using a new NXImage for our image.
                    238:  */
                    239: {
                    240:     NXRect eBounds, neBounds;
                    241: 
                    242:     [image free];
                    243:     image = newImage;
                    244:     [self getExtendedBounds:&eBounds];
                    245:     [image getSize:&neBounds.size];
                    246:     neBounds.size.width *= bounds.size.width / originalSize.width;
                    247:     neBounds.size.height *= bounds.size.height / originalSize.height;
                    248:     neBounds.origin.x = bounds.origin.x - floor((neBounds.size.width - bounds.size.width) / 2.0 + 0.5);
                    249:     neBounds.origin.y = bounds.origin.y - floor((neBounds.size.height - bounds.size.height) / 2.0 + 0.5);
                    250:     [self setBounds:&neBounds];
                    251:     [self getExtendedBounds:&neBounds];
                    252:     NXUnionRect(&eBounds, &neBounds);
                    253:     [image setDataRetained:YES];
                    254:     [image getSize:&originalSize];
                    255:     [image setScalable:YES];
                    256: 
                    257:     return neBounds;
                    258: }
                    259: 
                    260: - (NXRect)reinitFromPasteboard:(Pasteboard *)pboard
                    261: /*
                    262:  * Reset all of our instance variable based on extract an
                    263:  * NXImage from data in the the passed pboard.  Happens when
                    264:  * we update a link through Object Links.
                    265:  */
                    266: {
                    267:     NXRect neBounds;
                    268:     NXImage *newImage;
                    269: 
                    270:     newImage = [NXImage allocFromZone:[self zone]];
                    271:     if ((newImage = [newImage initFromPasteboard:pboard])) {
                    272:        [newImage setDataRetained:YES];
                    273:        if (checkImage(newImage)) return [self resetImage:newImage];
                    274:     }
                    275: 
                    276:     [newImage free];
                    277:     neBounds.origin.x = neBounds.origin.y = 0.0;
                    278:     neBounds.size.width = neBounds.size.height = 0.0;
                    279: 
                    280:     return neBounds;
                    281: }
                    282: 
                    283: - (NXRect)reinitFromFile:(const char *)file
                    284: /*
                    285:  * Reset all of our instance variable based on extract an
                    286:  * NXImage from the data in the passed file.  Happens when
                    287:  * we update a link through Object Links.
                    288:  */
                    289: {
                    290:     NXRect neBounds;
                    291:     NXImage *newImage;
                    292: 
                    293:     newImage = [[NXImage allocFromZone:[self zone]] init];
                    294:     if ([newImage loadFromFile:file]) {
                    295:        [newImage setDataRetained:YES];
                    296:        if (checkImage(newImage)) return [self resetImage:newImage];
                    297:     }
                    298: 
                    299:     [newImage free];
                    300:     neBounds.origin.x = neBounds.origin.y = 0.0;
                    301:     neBounds.size.width = neBounds.size.height = 0.0;
                    302: 
                    303:     return neBounds;
                    304: }
                    305: 
                    306: /* All those allocation/initialization method and only this one free method. */
                    307: 
                    308: - free
                    309: {
                    310:     [image free];
                    311:     return [super free];
                    312: }
                    313: 
                    314: /* Link methods */
                    315: 
                    316: - setLink:(NXDataLink *)aLink
                    317: /*
                    318:  * It's "might" be linked because we're linked now, but might
                    319:  * have our link broken in the future and the mightBeLinked flag
                    320:  * is only advisory and is never cleared.  It is used just so that
                    321:  * we know we might want to try to reestablish a link with this
                    322:  * Graphic after a cut/paste.  No biggie if there really is no
                    323:  * link associated with this any more.  In gvLinks.m, see
                    324:  * readLinkForGraphic:fromPasteboard:useNewIdentifier:, and in
                    325:  * gvPasteboard.m, see pasteFromPasteboard:andLink:at:.
                    326:  * If this Image is a link button, then we obviously never need
                    327:  * to update the link because we don't actually show the data
                    328:  * associated with the link (we just show that little link button).
                    329:  */
                    330: {
                    331:     NXDataLink *oldLink = link;
                    332:     link = aLink;
                    333:     gFlags.mightBeLinked = YES;
                    334:     if (amLinkButton) [link setUpdateMode:NX_UpdateNever];
                    335:     return oldLink;
                    336: }
                    337: 
                    338: - (NXDataLink *)link
                    339: {
                    340:     return link;
                    341: }
                    342: 
                    343: /* Event-handling */
                    344: 
                    345: - trackLinkButton:(NXEvent *)event at:(const NXPoint *)startPoint inView:(View *)view
                    346: /*
                    347:  * This method tracks that little link button.  Note that the link button is a diamond,
                    348:  * but we track the whole rectangle.  This is unfortunate, but we can't be sure that,
                    349:  * in the future, the shape of the link button might not change (thus, what we really
                    350:  * need is a NeXTSTEP function to track the thing!).  Anyway, we track it and if the 
                    351:  * mouse goes up inside the button, we openSource on the link (we wouldn't be here if
                    352:  * we didn't have a link).
                    353:  */
                    354: {
                    355:     NXPoint p;
                    356:     NXImage *realImage, *highImage, *imageToDraw;
                    357: 
                    358:     p = *startPoint;
                    359:     realImage = image;
                    360:     highImage = [[self class] highlightedLinkButtonImage:&bounds.size];
                    361:     image = imageToDraw = highImage;
                    362:     [self draw];
                    363:     [[view window] flushWindow];
                    364:     do {
                    365:        event = [NXApp getNextEvent:NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK];
                    366:        p = event->location;
                    367:        [view convertPoint:&p fromView:nil];
                    368:        imageToDraw = NXMouseInRect(&p, &bounds, NO) ? highImage : realImage;
                    369:        if (imageToDraw != image) {
                    370:            image = imageToDraw;
                    371:            [self draw];
                    372:            [[view window] flushWindow];
                    373:        }
                    374:     } while (event->type != NX_MOUSEUP);
                    375: 
                    376:     if (imageToDraw == highImage) {
                    377:        [link openSource];
                    378:        image = realImage;
                    379:        [self draw];
                    380:        [[view window] flushWindow];
                    381:     }
                    382: 
                    383:     return self;
                    384: }
                    385: 
                    386: - (BOOL)handleEvent:(NXEvent *)event at:(const NXPoint *)p inView:(View *)view
                    387: {
                    388:     if (NXMouseInRect(p, &bounds, NO)) {
                    389:        if (amLinkButton && !gFlags.selected && !(event->flags & (NX_CONTROLMASK|NX_SHIFTMASK|NX_ALTERNATEMASK))) {
                    390:            [self trackLinkButton:event at:p inView:view];
                    391:            return YES;
                    392:        } else if (link && (event->data.mouse.click == 2) && (amIcon || (event->flags & NX_CONTROLMASK))) {
                    393:            [NXApp getNextEvent:NX_MOUSEUPMASK];
                    394:            [link openSource];
                    395:            return YES;
                    396:        }
                    397:     }
                    398:     return NO;
                    399: }
                    400: 
                    401: /* Methods overridden from superclass to support links. */
                    402: 
                    403: - (int)cornerMask
                    404: /*
                    405:  * Link buttons are too small to have corners AND sides, so
                    406:  * we only let link buttons have knobbies on the corners.
                    407:  */
                    408: {
                    409:     if (amLinkButton) {
                    410:        return LOWER_LEFT_MASK|UPPER_LEFT_MASK|UPPER_RIGHT_MASK|LOWER_RIGHT_MASK;
                    411:     } else {
                    412:        return [super cornerMask];
                    413:     }
                    414: }
                    415: 
                    416: - (NXRect *)getExtendedBounds:(NXRect *)theRect
                    417: /*
                    418:  * We have to augment this because we might have a link frame
                    419:  * (if show links is on), so we have to extend our extended bounds
                    420:  * a bit.
                    421:  */
                    422: {
                    423:     NXRect linkBounds, *retval;
                    424:     float linkFrameThickness = NXLinkFrameThickness();
                    425: 
                    426:     linkBounds = bounds;
                    427:     linkBounds.origin.x -= linkFrameThickness;
                    428:     linkBounds.size.width += linkFrameThickness * 2.0;
                    429:     linkBounds.origin.y -= linkFrameThickness;
                    430:     linkBounds.size.height += linkFrameThickness;
                    431: 
                    432:     retval = [super getExtendedBounds:theRect];
                    433: 
                    434:     return NXUnionRect(&linkBounds, retval);
                    435: }
                    436: 
                    437: - (BOOL)constrainByDefault;
                    438: /*
                    439:  * Icons and link buttons look funny outside their natural
                    440:  * aspect ratio, so we constrain them (by default) to keep
                    441:  * their natural ratio.  You can still use the Alternate key
                    442:  * to NOT constrain these.
                    443:  */
                    444: {
                    445:     return (amLinkButton || amIcon);
                    446: }
                    447: 
                    448: /* Methods overridden from superclass */
                    449: 
                    450: - (BOOL)isValid
                    451: {
                    452:     return image ? YES : NO;
                    453: }
                    454: 
                    455: - (BOOL)isOpaque
                    456: {
                    457:     return [[image bestRepresentation] isOpaque];
                    458: }
                    459: 
                    460: - (float)naturalAspectRatio
                    461: {
                    462:     if (!originalSize.height) return 0.0;
                    463:     return originalSize.width / originalSize.height;
                    464: }
                    465: 
                    466: - draw
                    467: /*
                    468:  * If we are resizing, we just draw a gray box.
                    469:  * If not, then we simply see if our bounds have changed
                    470:  * and update the NXImage object if they have.  Then,
                    471:  * if we do not allow alpha (i.e. this is a TIFF image),
                    472:  * we paint a white background square (we don't allow
                    473:  * alpha in our TIFF images since it won't print and
                    474:  * Draw is WYSIWYG).  Finally, we SOVER the image.
                    475:  * If we are not keeping the cache around, we tell
                    476:  * NXImage to toss its cached version of the image
                    477:  * via the message recache.
                    478:  *
                    479:  * If we are linked to something and the user has chosen
                    480:  * "Show Links", then linkOutlinesAreVisible, so we must
                    481:  * draw a link border around ourself.
                    482:  */
                    483: {
                    484:     NXRect r;
                    485:     NXPoint p;
                    486:     NXSize currentSize;
                    487: 
                    488:     if (bounds.size.width < 1.0 || bounds.size.height < 1.0) return self;
                    489: 
                    490:     if (DrawStatus == Resizing) {
                    491:        PSsetgray(NX_DKGRAY);
                    492:        PSsetlinewidth(0.0);
                    493:        PSrectstroke(bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
                    494:     } else if (image) {
                    495:        p = bounds.origin;
                    496:        [image getSize:&currentSize];
                    497:        if (currentSize.width != bounds.size.width || currentSize.height != bounds.size.height) {
                    498:            if ([image isScalable]) {
                    499:                [image setSize:&bounds.size];
                    500:            } else {
                    501:                p.x = bounds.origin.x + floor((bounds.size.width - currentSize.width) / 2.0 + 0.5);
                    502:                p.y = bounds.origin.y + floor((bounds.size.height - currentSize.height) / 2.0 + 0.5);
                    503:            }
                    504:        }
                    505:        if ([[image bestRepresentation] isOpaque]) {
                    506:            PSsetgray(NX_WHITE);
                    507:            NXRectFill(&bounds);
                    508:        }
                    509:        [image composite:NX_SOVER toPoint:&p];
                    510:        if (dontCache && NXDrawingStatus == NX_DRAWING) [image recache];
                    511:        if ((NXDrawingStatus == NX_DRAWING) && !amLinkButton && [[link manager] areLinkOutlinesVisible]) {
                    512:            r.origin.x = floor(bounds.origin.x);
                    513:            r.origin.y = floor(bounds.origin.y);
                    514:            r.size.width = floor(bounds.origin.x + bounds.size.width + 0.99) - r.origin.x;
                    515:            r.size.height = floor(bounds.origin.y + bounds.size.height + 0.99) - r.origin.y;
                    516:            NXFrameLinkRect(&r, YES);   // YES means "is a destination link"
                    517:        }
                    518:     }
                    519: 
                    520:     return self;
                    521: }
                    522: 
                    523: /* Direct writing of EPS or TIFF. */
                    524: 
                    525: - (BOOL)canEmitEPS
                    526: /*
                    527:  * If we have a representation that can provide EPS directly, then,
                    528:  * if we are copying PostScript to the Pasteboard and this Image is the
                    529:  * only Graphic selected, then we might as well just have the EPS which
                    530:  * represents this Image go straight to the Pasteboard rather than
                    531:  * wrapping it up in the copyPSCodeInside: wrappers.  Of course, we
                    532:  * can only do that if we haven't been resized.
                    533:  *
                    534:  * See gvPasteboard.m's writePSToStream:.
                    535:  */
                    536: {
                    537:     List *reps = [image representationList];
                    538:     int i = [reps count];
                    539: 
                    540:     if (originalSize.width == bounds.size.width && originalSize.height == bounds.size.height) {
                    541:        while (i--) {
                    542:            if ([[reps objectAt:i] respondsTo:@selector(getEPS:length:)]) {
                    543:                return YES;
                    544:            }
                    545:        }
                    546:     }
                    547: 
                    548:     return NO;
                    549: }
                    550: 
                    551: - writeEPSToStream:(NXStream *)stream
                    552: /*
                    553:  * If canEmitEPS above returns YES, then we can write ourself out directly
                    554:  * as EPS.  This method does that.
                    555:  */
                    556: {
                    557:     List *reps = [image representationList];
                    558:     int i = [reps count];
                    559:     char *data;
                    560:     int length;
                    561: 
                    562:     while (i--) {
                    563:        if ([[reps objectAt:i] respondsTo:@selector(getEPS:length:)]) {
                    564:            [[reps objectAt:i] getEPS:&data length:&length];
                    565:            NXWrite(stream, data, length);
                    566:            return self;                        // should I free data before returning?
                    567:        }
                    568:     }
                    569: 
                    570:     return self;
                    571: }
                    572: 
                    573: - (BOOL)canEmitTIFF
                    574: /*
                    575:  * Similar to canEmitEPS, except its for TIFF.
                    576:  */
                    577: {
                    578:     return (originalSize.width == bounds.size.width && originalSize.height == bounds.size.height);
                    579: }
                    580: 
                    581: - writeTIFFToStream:(NXStream *)stream
                    582: /*
                    583:  * Ditto above.
                    584:  */
                    585: {
                    586:     [image writeTIFF:stream allRepresentations:YES];
                    587:     return self;
                    588: }
                    589: 
                    590: /* Caching. */
                    591: 
                    592: - setCacheable:(BOOL)flag
                    593: {
                    594:     dontCache = flag ? NO : YES;
                    595:     return self;
                    596: }
                    597: 
                    598: - (BOOL)isCacheable
                    599: {
                    600:     return !dontCache;
                    601: }
                    602: 
                    603: /* Archiving. */
                    604: 
                    605: - write:(NXTypedStream *)stream
                    606: /*
                    607:  * All that is needed to archive the NXImage.
                    608:  */
                    609: {
                    610:     [super write:stream];
                    611:     NXWriteType(stream, "c", &amLinkButton);
                    612:     NXWriteType(stream, "c", &amIcon);
                    613:     if (!amLinkButton) {
                    614:        NXWriteObject(stream, image);
                    615:        NXWriteSize(stream, &originalSize);
                    616:     }
                    617:     return self;
                    618: }
                    619: 
                    620: - read:(NXTypedStream *)stream
                    621: /*
                    622:  * This contains lots of compatibility code for
                    623:  * interim versions.  See if you can figure out the
                    624:  * various ways we approached archiving link info!
                    625:  */
                    626: {
                    627:     BOOL alphaOk;
                    628:     NXRect savedBounds;
                    629:     int version, linkNumber;
                    630: 
                    631:     [super read:stream];
                    632:     version = NXTypedStreamClassVersion(stream, "Image");
                    633:     if (version > 5) NXReadType(stream, "c", &amLinkButton);
                    634:     if (version > 6) NXReadType(stream, "c", &amIcon);
                    635:     if (amLinkButton) {
                    636:        savedBounds = bounds;
                    637:        [self doInitFromImage:[[NXImage findImageNamed:"NXLinkButton"] copy]];
                    638:        bounds = savedBounds;
                    639:     } else {
                    640:        image = NXReadObject(stream);
                    641:        NXReadSize(stream, &originalSize);
                    642:     }
                    643:     if (version <= 2) NXReadTypes(stream, "c", &alphaOk);
                    644:     if (version == 4) {
                    645:        NXReadObject(stream);   // used to be the NXDataLink
                    646:     } else if (version > 2 && version < 6) {
                    647:        NXReadTypes(stream, "i", &linkNumber);
                    648:     }
                    649: 
                    650:     return self;
                    651: }
                    652: 
                    653: @end

unix.superglobalmegacorp.com

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