/** DiagramView.m Author: Matt Rice Date: Oct 2006 This file is part of DBModeler. DBModeler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. DBModeler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with DBModeler; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA **/ #include "DiagramView.h" #include "EntityView.h" #include "AttributeCell.h" #ifdef NeXT_Foundation_LIBRARY #include #else #include #endif #ifdef NeXT_GUI_LIBRARY #include #else #include #include #include #include #include #include #include #endif #include #include #include #include #include #include #include @implementation DiagramView - (id) initWithFrame:(NSRect)frameRect { self = [super initWithFrame:frameRect]; _shownEntities = [NSMutableDictionary new]; _relationships = [NSMutableArray new]; _subview_order = [NSMutableArray new]; _bgColor = RETAIN([NSColor colorWithCalibratedRed:0.881437 green:0.941223 blue:1.0 alpha:1.0]); [self registerForDraggedTypes:[NSArray arrayWithObject:NSColorPboardType]]; return self; } - (void) dealloc { RELEASE(_shownEntities); RELEASE(_relationships); RELEASE(_subview_order); RELEASE(_bgColor); RELEASE(_model); [super dealloc]; } - (BOOL) prepareForDragOperation:(id )sender { return YES; } - (NSDragOperation) draggingEntered:(id )sender { return [sender draggingSourceOperationMask]; } - (BOOL) performDragOperation:(id )sender { NSPasteboard *pb = [sender draggingPasteboard]; NSColor *color = [NSColor colorFromPasteboard:pb]; ASSIGN(_bgColor, color); [self setNeedsDisplay:YES]; return YES; } - (BOOL) isFlipped { return YES; } - (void) setModel:(EOModel *)model { ASSIGN(_model, model); } - (void) showEntity:(NSString *)name { EntityView *ev; if (!(ev = [_shownEntities objectForKey:name])) { EOEntity *entity = [_model entityNamed:name]; NSArray *attribs = [entity attributes]; NSRect vis = [[self enclosingScrollView] documentVisibleRect]; NSRect evFrame; NSPoint toPoint; int i, c = [attribs count]; ev = [[EntityView alloc] initWithFrame:NSMakeRect(0,0,1,1)]; [ev setTitle:[entity name]]; [ev setNumberOfAttributes:c]; for (i = 0; i < c; i++) { EOAttribute *attrib = [attribs objectAtIndex:i]; AttributeCell *cell = [ev cellAtRow:i]; [cell setStringValue:[attrib name]]; [cell setLock:[[entity attributesUsedForLocking] containsObject:attrib]]; [cell setProp:[[entity classProperties] containsObject:attrib]]; [cell setKey:[[entity primaryKeyAttributes] containsObject:attrib]]; } [ev sizeToFit]; evFrame = [ev frame]; /* this "layout mechanism" is pure evil... */ toPoint.x = floor(vis.origin.x + ((vis.size.width - evFrame.size.width) * rand()/(RAND_MAX + vis.origin.x))); toPoint.y = floor(vis.origin.y + ((vis.size.height - evFrame.size.height) * rand()/(RAND_MAX + vis.origin.y))); [ev setFrameOrigin:toPoint]; [_shownEntities setObject:ev forKey:[entity name]]; [self addSubview:ev]; [_subview_order addObject:ev]; [self setupRelationships]; } else { EOEntity *entity = [_model entityNamed:name]; NSArray *attribs = [entity attributes]; int i, c = [attribs count]; [ev setTitle:[entity name]]; [ev setNumberOfAttributes:c]; for (i = 0; i < c; i++) { EOAttribute *attrib = [attribs objectAtIndex:i]; AttributeCell *cell = [ev cellAtRow:i]; [cell setStringValue:[attrib name]]; [cell setLock:[[entity attributesUsedForLocking] containsObject:attrib]]; [cell setProp:[[entity classProperties] containsObject:attrib]]; [cell setKey:[[entity primaryKeyAttributes] containsObject:attrib]]; } [ev sizeToFit]; [ev setNeedsDisplay:YES]; } } int sortSubviews(id view1, id view2, void *context) { DiagramView *self = context; unsigned idx1, idx2; idx1 = [self->_subview_order indexOfObject:view1]; idx2 = [self->_subview_order indexOfObject:view2]; return (idx1 < idx2) ? NSOrderedAscending : NSOrderedDescending; } - (void) orderViewFront:(NSView *)v { int idx = [_subview_order indexOfObject:v]; RETAIN(v); [_subview_order removeObjectAtIndex:idx]; [_subview_order addObject:v]; RELEASE(v); [self sortSubviewsUsingFunction:(int (*)(id, id, void *))sortSubviews context:self]; [self setNeedsDisplay:YES]; } - (void) setupRelationships { int i,c; NSArray *stuff = [_shownEntities allKeys]; [_relationships removeAllObjects]; for (i = 0, c = [stuff count]; i < c; i++) { int j, d; NSString *entName = [stuff objectAtIndex:i]; EOEntity *ent = [_model entityNamed:entName]; NSArray *rels = [ent relationships]; for (j = 0, d = [rels count]; j < d; j++) { EORelationship *rel = [rels objectAtIndex:j]; EOEntity *dest = [rel destinationEntity]; id srcName = [ent name]; id destName = [dest name]; EntityView *from = [_shownEntities objectForKey:srcName]; EntityView *to = [_shownEntities objectForKey:destName]; NSArray *srcAttribs = [rel sourceAttributes]; NSArray *destAttribs = [rel destinationAttributes]; int k, e; for (k = 0, e = [srcAttribs count]; k < e; k++) { id sAttrib = [srcAttribs objectAtIndex:k]; id dAttrib = [destAttribs objectAtIndex:k]; int sIdx = [[ent attributes] indexOfObject:sAttrib]; int dIdx = [[dest attributes] indexOfObject:dAttrib]; NSRect fromRect = [from attributeRectAtRow:sIdx]; NSRect toRect = [to attributeRectAtRow:dIdx]; NSRect fromViewFrame = [from frame]; NSRect toViewFrame = [to frame]; NSPoint midPoint; NSPoint tmp; float arrowOffset; NSBezierPath *path = [NSBezierPath bezierPath]; BOOL fromRight; BOOL toRight; [path setLineWidth:2]; fromRect.origin.y += fromViewFrame.origin.y; toRect.origin.y += toViewFrame.origin.y; /* which side of the EntityView the arrow line will be connecting * to, for the source and destination entities */ fromRight = (fromViewFrame.origin.x - 40 < toViewFrame.origin.x + toViewFrame.size.width); toRight = (toViewFrame.origin.x - 40 < fromViewFrame.origin.x + fromViewFrame.size.width); if (fromRight) { fromRect.origin.x = fromViewFrame.origin.x + fromViewFrame.size.width + 5; } else { fromRect.origin.x = fromViewFrame.origin.x - 5; } if (toRight) { toRect.origin.x = toViewFrame.origin.x + toViewFrame.size.width; toRect.origin.x += 5; /* <- */ arrowOffset = -5.0; } else { toRect.origin.x = toViewFrame.origin.x; toRect.origin.x -= 5; /* -> */ arrowOffset = 5.0; } fromRect.origin.y = NSMidY(fromRect); toRect.origin.y = NSMidY(toRect); /* every line segment is drawn forwards and backwards so we dont * end up with lightning bolts when filling the arrow */ /* a recursive relationship... * Don't think they are particularly useful but... */ if (fromRect.origin.y == toRect.origin.y && fromRect.origin.x == toRect.origin.x) { [path moveToPoint:NSMakePoint(toRect.origin.x + 15, toRect.origin.y)]; [path lineToPoint:NSMakePoint(toRect.origin.x + 15, toRect.origin.y + 5)]; [path lineToPoint:NSMakePoint(toRect.origin.x + 15, toRect.origin.y)]; [path moveToPoint:NSMakePoint(toRect.origin.x + 15, toRect.origin.y + 5)]; [path lineToPoint:NSMakePoint(toRect.origin.x + 20, toRect.origin.y + 5)]; [path lineToPoint:NSMakePoint(toRect.origin.x + 15, toRect.origin.y + 5)]; [path moveToPoint:NSMakePoint(toRect.origin.x + 20, toRect.origin.y + 5)]; [path lineToPoint:NSMakePoint(toRect.origin.x + 20, toRect.origin.y)]; [path lineToPoint:NSMakePoint(toRect.origin.x + 20, toRect.origin.y + 5)]; } if ((fromRight || toRight) && !(fromRight && toRight)) { /* a line like... +----- * | * -----+ * (from the right side, to the left side or vice versa) */ [path moveToPoint:fromRect.origin]; midPoint.x = (fromRect.origin.x + toRect.origin.x) / 2; midPoint.y = fromRect.origin.y; [path lineToPoint:midPoint]; [path lineToPoint:fromRect.origin]; [path moveToPoint:midPoint]; tmp = midPoint; midPoint.x = (fromRect.origin.x + toRect.origin.x) / 2; midPoint.y = toRect.origin.y; [path lineToPoint:midPoint]; [path lineToPoint:tmp]; [path moveToPoint:midPoint]; [path lineToPoint:toRect.origin]; [path lineToPoint:midPoint]; } else if (fromRight && toRight) { /* need to --+ or -------+ <- joint end or start. * make a line | | * like... -------+ --+ <- joint start or end. * from the right side to the right side. */ NSPoint jointStart; NSPoint jointEnd; if (toRect.origin.x + toRect.size.width < fromRect.origin.x + fromRect.size.width) { jointStart = NSMakePoint(fromRect.origin.x + 20, fromRect.origin.y); jointEnd = NSMakePoint(fromRect.origin.x + 20, toRect.origin.y); } else { jointStart = NSMakePoint(toRect.origin.x + 20, fromRect.origin.y); jointEnd = NSMakePoint(toRect.origin.x + 20, toRect.origin.y); } [path moveToPoint:fromRect.origin]; [path lineToPoint:jointStart]; [path lineToPoint:fromRect.origin]; [path moveToPoint:jointStart]; [path lineToPoint:jointEnd]; [path lineToPoint:jointStart]; [path moveToPoint:jointEnd]; [path lineToPoint:toRect.origin]; [path lineToPoint:jointEnd]; } /* draw arrows.. */ if ([rel isToMany]) { [path moveToPoint:toRect.origin]; [path lineToPoint:NSMakePoint(toRect.origin.x - arrowOffset, toRect.origin.y + arrowOffset)]; [path lineToPoint:NSMakePoint(toRect.origin.x - arrowOffset, toRect.origin.y - arrowOffset)]; [path lineToPoint:toRect.origin]; toRect.origin.x -= arrowOffset; [path moveToPoint:toRect.origin]; [path lineToPoint:NSMakePoint(toRect.origin.x - arrowOffset, toRect.origin.y + arrowOffset)]; [path lineToPoint:NSMakePoint(toRect.origin.x - arrowOffset, toRect.origin.y - arrowOffset)]; [path lineToPoint:toRect.origin]; } else { [path moveToPoint:toRect.origin]; [path lineToPoint:NSMakePoint(toRect.origin.x - arrowOffset, toRect.origin.y + arrowOffset)]; [path lineToPoint:NSMakePoint(toRect.origin.x - arrowOffset, toRect.origin.y - arrowOffset)]; [path lineToPoint:toRect.origin]; } [path closePath]; [_relationships addObject:path]; } } } } - (void) mouseDown:(NSEvent *)ev { /* create new relationshp??*/ } - (BOOL) autoscroll:(NSEvent *)ev { NSPoint pt = [self convertPoint:[ev locationInWindow] fromView:nil]; NSRect r = _frame; BOOL flag; if (!NSPointInRect(pt, r)) { if (pt.x > r.size.width) r.size.width = pt.x; if (pt.y > r.size.height) r.size.height = pt.y; [self setFrameSize:r.size]; } flag = [super autoscroll:ev]; return flag; } - (void) drawRect:(NSRect)aRect { int i, c; [_bgColor set]; NSRectFill([self frame]); [self setupRelationships]; for (i = 0, c = [_relationships count]; i < c; i++) { [[NSColor blackColor] set]; NSBezierPath *path = [_relationships objectAtIndex:i]; [path stroke]; [path fill]; } } @end