1/*
2    Copyright (c) 1998-2005 Benhur Stein
3
4    This file is part of Paj�.
5
6    Paj� is free software; you can redistribute it and/or modify it under
7    the terms of the GNU Lesser General Public License as published by the
8    Free Software Foundation; either version 2 of the License, or (at your
9    option) any later version.
10
11    Paj� is distributed in the hope that it will be useful, but WITHOUT ANY
12    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13    FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
14    for more details.
15
16    You should have received a copy of the GNU Lesser General Public License
17    along with Paj�; if not, write to the Free Software Foundation, Inc.,
18    59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
19*/
20/*
21 * DrawView.m
22 *
23 * a View that draws lines
24 *
25 * 19960115 BS  creation
26 * 19960212 BS  clean-up
27 */
28
29#include "DrawView.h"
30#include "STController.h"
31
32#include "../General/NSUserDefaults+Additions.h"
33#include "../General/Macros.h"
34#include "../General/EntityChunk.h"
35
36#include <math.h>
37
38#define DefaultsPrefix NSStringFromClass([self class])
39#define DefaultsKey(name) [DefaultsPrefix stringByAppendingString:name]
40
41@implementation DrawView
42
43- (void)dealloc
44{
45    Assign(startTime, nil);
46    Assign(endTime, nil);
47    Assign(backgroundColor, nil);
48    Assign(selectedBackgroundColor, nil);
49    Assign(entityNameAttributes, nil);
50    Assign(cursorTimeFormat, nil);
51    [super dealloc];
52}
53
54- (NSColor *)backgroundColor
55{
56    return backgroundColor;
57}
58
59- (NSColor *)selectedBackgroundColor
60{
61    return selectedBackgroundColor;
62}
63
64- (void)setBackgroundColor:(NSColor *)color
65{
66    Assign(backgroundColor, color);
67    [[NSUserDefaults standardUserDefaults]
68              setColor:backgroundColor
69                forKey:DefaultsKey(@"BackgroundColor")];
70    [self setNeedsDisplay:YES];
71}
72
73- (void)setSelectedBackgroundColor:(NSColor *)color
74{
75    Assign(selectedBackgroundColor, color);
76    [[NSUserDefaults standardUserDefaults]
77              setColor:selectedBackgroundColor
78                forKey:DefaultsKey(@"SelectedBackgroundColor")];
79    [self setNeedsDisplay:YES];
80}
81
82- (void)setFilter:(PajeFilter *)newFilter
83{
84    filter = newFilter;
85}
86
87- (PajeFilter *)filter
88{
89    return filter;
90}
91
92- (void)setController:(STController *)c
93{
94    controller = c;
95}
96
97- (void)hierarchyChanged
98{
99    Assign(startTime, [filter startTime]);
100    Assign(endTime, [filter endTime]);
101    NSDebugMLLog(@"tim", @"Hier changed. times=[%@ %@]", startTime, endTime);
102
103    if (startTime != nil && endTime != nil) {
104        [self adjustSize];
105    }
106}
107
108- (void)dataChangedForEntityType:(PajeEntityType *)entityType
109{
110    [self adjustSize];
111}
112
113- (void)colorChangedForEntityType:(PajeEntityType *)entityType
114{
115    [self setNeedsDisplay:YES];
116}
117
118- (void)orderChangedForContainerType:(PajeContainerType *)containerType
119{
120    [self adjustSize];
121}
122
123- (void)awakeFromNib
124{
125    NSCursor *cursor;
126    NSImage *cursorImage;
127    NSString *cursorPath;
128
129    // initialize instance variables
130
131    pointsPerSecond = [[NSUserDefaults standardUserDefaults]
132                                doubleForKey:DefaultsKey(@"PointsPerSecond")];
133    if (pointsPerSecond == 0) {
134        pointsPerSecond = 10000;
135    }
136    smallEntityWidth = [[NSUserDefaults standardUserDefaults]
137                              integerForKey:DefaultsKey(@"SmallEntityWidth")];
138    hasZoomed = NO;
139    filter = nil;
140    //trackingRectTag = 0;
141    timeUnitDivisor = 1;
142    Assign(cursorTimeFormat, @"%.6f s");
143
144    [zoomToSelectionButton setEnabled:NO];
145
146//    [[self window] setBackingType:NSBackingStoreNonretained];
147
148    // get background color from defaults
149    Assign(backgroundColor, [[NSUserDefaults standardUserDefaults]
150                                colorForKey:DefaultsKey(@"BackgroundColor")]);
151    if (backgroundColor == nil) {
152        Assign(backgroundColor, [NSColor controlBackgroundColor]);
153    }
154    Assign(selectedBackgroundColor, [[NSUserDefaults standardUserDefaults]
155                         colorForKey:DefaultsKey(@"SelectedBackgroundColor")]);
156    if (selectedBackgroundColor == nil) {
157        Assign(selectedBackgroundColor, [NSColor controlLightHighlightColor]);
158    }
159
160    // initialize the cursor
161    cursorPath = [[NSBundle bundleForClass:[self class]]
162                     pathForImageResource:@"crosscursor"];
163    cursorImage = [[NSImage alloc] initWithContentsOfFile:cursorPath];
164    cursor = [[NSCursor alloc] initWithImage:cursorImage hotSpot:NSMakePoint(7,9)];
165    [cursorImage release];
166    if (cursor != nil) {
167        [[self enclosingScrollView] setDocumentCursor:cursor];
168        [cursor release];
169    }
170
171#ifdef GNUSTEP
172[[cursorTimeField window] performSelector:@selector(makeKeyAndOrderFront:) withObject:self afterDelay:0];
173#endif
174
175    // register to receive color drag-and-drops
176    [self registerForDraggedTypes:[NSArray arrayWithObject:NSColorPboardType]];
177
178//    [[EntityTypeInspector alloc] initWithDelegate:self];
179
180    [[self enclosingScrollView] setHasHorizontalRuler:YES];
181    [[[self enclosingScrollView] horizontalRulerView] setReservedThicknessForMarkers:0.0];
182    [[self enclosingScrollView] setBackgroundColor:[self backgroundColor]/*[NSColor whiteColor]*/];
183    [[self enclosingScrollView] setDrawsBackground:YES/*NO*/];
184
185
186    entityNameAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
187                            [NSFont systemFontOfSize:8], @"NSFontAttributeName",
188                            nil];
189
190    //TODO: should register as tool
191}
192
193
194
195- (NSRect)adjustScroll:(NSRect)newVisible
196{
197    if (hasZoomed) {
198        float lastX = TIMEtoX(endTime);
199        float newX = TIMEtoX(oldMiddleTime) - NSWidth(newVisible) / 2;
200        if ( (newX + NSWidth(newVisible)) > lastX )
201            newX = lastX - NSWidth(newVisible);
202        if (newX < 0) newX = 0;
203        newVisible.origin.x = newX;
204        hasZoomed = NO;
205        Assign(oldMiddleTime, nil);
206    }
207    newVisible.origin.x = (int)newVisible.origin.x;
208    return newVisible;
209}
210
211- (void)windowDidResize:(NSNotification *)aNotification
212{
213    [self adjustSize];
214}
215
216- (void)setRulerUnit
217{
218    NSString *unitName;
219    NSString *unitAbbreviation;
220    NSRulerView *ruler;
221    double logUnitsToPoints;
222
223    if (pointsPerSecond > 300000000) {
224        timeUnitDivisor = 1000000000;
225        unitName = @"nanoseconds";
226        unitAbbreviation = @"ns";
227    } else if (pointsPerSecond > 300000) {
228        timeUnitDivisor = 1000000;
229        unitName = @"microseconds";
230        unitAbbreviation = @"\xb5s";
231    } else if (pointsPerSecond > 300) {
232        timeUnitDivisor = 1000;
233        unitName = @"milliseconds";
234        unitAbbreviation = @"ms";
235    } else if (pointsPerSecond > 0.1) {
236        timeUnitDivisor = 1;
237        unitName = @"seconds";
238        unitAbbreviation = @"s";
239    } else if (pointsPerSecond > .001) {
240        timeUnitDivisor = 1.0/3600.0;
241        unitName = @"hours";
242        unitAbbreviation = @"h";
243    } else {
244        timeUnitDivisor = 1.0/3600.0/24.0;
245        unitName = @"days";
246        unitAbbreviation = @"d";
247    }
248
249    logUnitsToPoints = log10(timeUnitDivisor/pointsPerSecond);
250    int decimals;
251    if (logUnitsToPoints > 0) {
252        decimals = 0;
253    } else {
254        decimals = -logUnitsToPoints + 0.8;
255    }
256    NSString *format;
257    format = [NSString stringWithFormat:@"%%.%df %@",
258                                decimals, unitAbbreviation];
259    Assign(cursorTimeFormat, format);
260
261    ruler = [[self enclosingScrollView] horizontalRulerView];
262    if (ruler) {
263        // sets ruler scale
264        NSArray *upArray;
265        NSArray *downArray;
266
267        upArray = [NSArray arrayWithObjects:
268            [NSNumber numberWithFloat:5.0], [NSNumber numberWithFloat:2.0], nil];
269        downArray = [NSArray arrayWithObjects:
270            [NSNumber numberWithFloat:0.5], [NSNumber numberWithFloat:0.2], nil];
271        [NSRulerView registerUnitWithName:unitName
272                             abbreviation:unitAbbreviation
273             unitToPointsConversionFactor:pointsPerSecond/timeUnitDivisor
274                              stepUpCycle:upArray
275                            stepDownCycle:downArray];
276        [ruler setMeasurementUnits:unitName];
277    }
278}
279
280- (void)removeFromSuperview
281{
282    if (trackingRectTag != 0) {
283        [self removeTrackingRect:trackingRectTag];
284        trackingRectTag = 0;
285    }
286    [super removeFromSuperview];
287}
288
289// set tracking rect to visible rectangle of view. should be called any time
290// the view frame is changed
291- (void)resetTrackingRect
292{
293    if ([self window] == nil) {
294        return;
295    }
296    if (trackingRectTag != 0) {
297        [self removeTrackingRect:trackingRectTag];
298    }
299    trackingRectTag = [self addTrackingRect:[self visibleRect]
300                                      owner:self
301                                   userData:NULL
302                               assumeInside:NO];
303}
304
305- (void)adjustSize
306{
307    NSRect newBounds;
308    NSScrollView *scrollView = [self enclosingScrollView];
309    NSRulerView *ruler = [scrollView horizontalRulerView];
310    id rootInstance = [filter rootInstance];
311
312    [self setRulerUnit];
313
314    [self performSelector:@selector(verifyTimes:)
315               withObject:self
316               afterDelay:0.0];
317
318    if (rootInstance != nil) {
319        newBounds = [[controller rootLayout] rectOfInstance:rootInstance];
320
321        newBounds.origin.x = TIMEtoX(startTime);
322        newBounds.size.width = TIMEtoX(endTime) - newBounds.origin.x + 3;
323        newBounds.size.height += 1;
324        [self setBoundsOrigin:newBounds.origin];
325        [self setFrameSize:newBounds.size];
326
327        [ruler setOriginOffset:
328                    TIMEtoX([NSDate dateWithTimeIntervalSinceReferenceDate:0])];
329        [scrollView tile];
330
331        [self resetTrackingRect];
332    }
333}
334
335- (void)verifyTimes:sender
336{
337    //FIXME: shouldn't be necessary; encapsulator should be more intelligent
338    NSScrollView *scrollView = [self enclosingScrollView];
339    [filter verifyStartTime:/*startTime*/XtoTIME(NSMinX([self convertRect:[scrollView frame]
340                                                    fromView:scrollView]))
341                    endTime:XtoTIME(NSMaxX([self convertRect:[scrollView frame]
342                                                    fromView:scrollView]))];
343}
344
345- (void)setFrame:(NSRect)frame
346{
347    [super setFrame:frame];
348
349    [self resetTrackingRect];
350}
351
352- (void)setBounds:(NSRect)bounds
353{
354    [super setBounds:bounds];
355
356    [self resetTrackingRect];
357}
358
359- (void)viewWillMoveToWindow:(NSWindow *)newWindow
360{
361    if ([self window] != nil && trackingRectTag != 0) {
362        [self removeTrackingRect:trackingRectTag];
363        trackingRectTag = 0;
364    }
365    [super viewWillMoveToWindow:newWindow];
366    [self resetTrackingRect];
367}
368
369
370/*
371 * Changing scales
372 * ------------------------------------------------------------------------
373 */
374
375- (void)adjustTimeLimits
376{
377    NSDate *startTimeLimit;
378    NSDate *endTimeLimit;
379    NSDate *traceStartTime;
380    NSDate *traceEndTime;
381
382    traceStartTime = [controller startTime];
383    traceEndTime = [controller endTime];
384    if (oldMiddleTime != nil) {
385        startTimeLimit = XtoTIME(TIMEtoX(oldMiddleTime) - 20000);
386    } else {
387        startTimeLimit = traceStartTime;
388    }
389    if ([startTimeLimit isEarlierThanDate:traceStartTime]) {
390        startTimeLimit = traceStartTime;
391        endTimeLimit = XtoTIME(TIMEtoX(startTimeLimit) + 40000);
392        if ([endTimeLimit isLaterThanDate:traceEndTime]) {
393            endTimeLimit = traceEndTime;
394        }
395    } else {
396        endTimeLimit = XtoTIME(TIMEtoX(startTimeLimit) + 40000);
397        if ([endTimeLimit isLaterThanDate:traceEndTime]) {
398            endTimeLimit = traceEndTime;
399            startTimeLimit = XtoTIME(TIMEtoX(endTimeLimit) - 40000);
400            if ([startTimeLimit isEarlierThanDate:traceStartTime]) {
401                startTimeLimit = traceStartTime;
402            }
403        }
404    }
405    Assign(startTime, startTimeLimit);
406    Assign(endTime, endTimeLimit);
407    NSDebugMLLog(@"tim", @"times=[%@ %@] trace=[%@ %@]", startTime, endTime, traceStartTime, traceEndTime);
408}
409
410- (void)setPointsPerSecond:(double)pps
411{
412    pointsPerSecond = pps;
413
414    [[NSUserDefaults standardUserDefaults]
415                                setDouble:pointsPerSecond
416                                   forKey:DefaultsKey(@"PointsPerSecond")];
417    hasZoomed = YES;
418
419    [self adjustTimeLimits];
420
421    [controller changedTimeScale];
422}
423
424- (void)saveMiddleTime
425{
426    if (startTime != nil) {
427        Assign(oldMiddleTime, XtoTIME(NSMidX([self visibleRect])));
428    }
429}
430
431- (void)getPointsPerSecondFrom:(id)source
432{
433    Assign(oldMiddleTime, XtoTIME(NSMidX([self visibleRect])));
434    [self setPointsPerSecond:[source doubleValue]];
435}
436
437- (void)doubleTimeScale:sender
438{
439    Assign(oldMiddleTime, XtoTIME(NSMidX([self visibleRect])));
440    [self setPointsPerSecond:pointsPerSecond * 2];
441//[self performSelector:@selector(getPointsPerSecondFrom:) withObject:[NSNumber numberWithDouble:pointsPerSecond*1.1] afterDelay:0.1];
442//[self performSelector:@selector(getPointsPerSecondFrom:) withObject:[NSNumber numberWithDouble:pointsPerSecond*1.5] afterDelay:0.2];
443//[self performSelector:@selector(getPointsPerSecondFrom:) withObject:[NSNumber numberWithDouble:pointsPerSecond*2.0] afterDelay:0.3];
444}
445
446- (void)halveTimeScale:sender
447{
448    Assign(oldMiddleTime, XtoTIME(NSMidX([self visibleRect])));
449    [self setPointsPerSecond:pointsPerSecond / 2];
450//[self performSelector:@selector(getPointsPerSecondFrom:) withObject:[NSNumber numberWithDouble:pointsPerSecond/1.1] afterDelay:0.1];
451//[self performSelector:@selector(getPointsPerSecondFrom:) withObject:[NSNumber numberWithDouble:pointsPerSecond/1.5] afterDelay:0.2];
452//[self performSelector:@selector(getPointsPerSecondFrom:) withObject:[NSNumber numberWithDouble:pointsPerSecond/2.0] afterDelay:0.3];
453}
454
455- (void)zoomToSelection:sender
456{
457    NSRect visible;
458
459    if (!selectionExists) {
460        return;
461    }
462
463    visible = [[self superview] frame];
464    Assign(oldMiddleTime, XtoTIME((TIMEtoX(selectionStartTime)
465                                   + TIMEtoX(selectionEndTime)) / 2));
466    [self setPointsPerSecond: NSWidth(visible)
467                 / [selectionEndTime timeIntervalSinceDate:selectionStartTime]];
468}
469
470- (IBAction)getSmallEntityWidthFrom:sender
471{
472    smallEntityWidth = [sender intValue];
473    [self setNeedsDisplay:YES];
474}
475
476- (double)pointsPerSecond
477{
478    return pointsPerSecond;
479}
480
481- (NSDate *)startTime
482{
483    return startTime;
484}
485
486- (double)timeToX:(NSDate *)t
487{
488    if (startTime == nil) return 0;
489    return TIMEtoX(t);
490}
491
492
493/*
494 * Drawing
495 * ------------------------------------------------------------------------
496 */
497
498- (BOOL)isFlipped
499// y-axis is inverted (y-coords augment from top to bottom)
500{
501    return YES;
502}
503
504- (BOOL)isOpaque
505// this view draws every pixel it owns
506{
507    return YES;
508}
509
510
511- (void)redrawEntities:(NSArray *)entities
512{
513    id eEnum, entity;
514
515    if (entities != nil) {
516        eEnum = [entities objectEnumerator];
517        while ((entity = [eEnum nextObject]) != nil) {
518            [self setNeedsDisplayInRect:[self highlightRectForEntity:entity]];
519        }
520    }
521}
522
523
524- (NSArray *)highlightedEntities
525{
526    return highlightedEntities;
527}
528
529- (void)setHighlightedEntities:(NSArray *)entities
530// highlights entities, taking care of unhighlighting previous
531{
532    if (highlightedEntities != nil) {
533        [self redrawEntities:highlightedEntities];
534    }
535    Assign(highlightedEntities, entities);
536    if (highlightedEntities != nil) {
537        [self redrawEntities:highlightedEntities];
538    }
539}
540
541- (void)setCursorEntity:(PajeEntity *)entity
542// highlights entity (and related entities), taking care of unhighlighting previous
543{
544    if (entity == cursorEntity) {
545        return;
546    }
547    Assign(cursorEntity, entity);
548    if (cursorEntity != nil) {
549        NSArray *related = [filter relatedEntitiesForEntity:cursorEntity];
550        [self setHighlightedEntities:[related arrayByAddingObject:cursorEntity]];
551        [entityNameField setStringValue:[filter descriptionForEntity:cursorEntity]];
552    } else {
553        [self setHighlightedEntities:nil];
554        [entityNameField setStringValue:@""];
555    }
556}
557
558
559- (void)drawBackgroundInRect:(NSRect)rect
560// draws the background
561{
562    // draw background
563    [backgroundColor set];
564    NSRectFill(rect);
565
566    // draw selection background
567    if (selectionExists) {
568        float selectionStart = TIMEtoX(selectionStartTime);
569        float selectionWidth = TIMEtoX(selectionEndTime) - selectionStart;
570        NSRect selectionRect = NSMakeRect(selectionStart, NSMinY(rect),
571                                          selectionWidth, NSMaxY(rect));
572#ifdef GNUSTEP
573        // GNUstep can't draw big rects.
574        selectionRect = NSIntersectionRect(selectionRect, cutRect);
575#endif
576
577        [selectedBackgroundColor set];
578        NSRectFill(selectionRect);
579    }
580}
581
582
583- (void)drawRect:(NSRect)rect
584{
585    if ([self window] == nil) return;
586    if (startTime == nil) return;
587
588[self verifyTimes:self];
589rect = NSInsetRect(rect, -10, -10);
590
591#ifdef GNUSTEP
592    // Big rectangles are not drawn by GNUstep. Cut them with cutRect.
593    cutRect = NSInsetRect([self visibleRect], -200, -200);
594#endif
595
596    NS_DURING
597
598        // set blackground
599        [self drawBackgroundInRect:rect];
600
601        [self drawInstance:[controller rootInstance]
602              ofDescriptor:[controller rootLayout]
603                    inRect:rect];
604    NS_HANDLER
605        NSLog(@"Ignoring exception caught inside drawRect: %@", localException);
606    NS_ENDHANDLER
607
608    //FIXME: put this in controller
609    [EntityChunk emptyLeastRecentlyUsedChunks];
610}
611
612
613
614
615
616
617/*
618 * Selection control
619 * ------------------------------------------------------------------------
620 */
621
622- (BOOL)isPointInSelection:(NSPoint)point
623{
624    NSDate *pointInTime;
625
626    if (!selectionExists) {
627        return NO;
628    }
629
630    pointInTime = XtoTIME(point.x);
631    return [pointInTime isLaterThanDate:selectionStartTime]
632           && [pointInTime isEarlierThanDate:selectionEndTime];
633}
634
635- (void)timeSelectionChanged
636{
637    NSRect redisplayRect;
638    NSDate *newSelectionStartTime;
639    NSDate *newSelectionEndTime;
640    double ox1, ox2, nx1, nx2;
641
642    newSelectionStartTime = [controller selectionStartTime];
643    newSelectionEndTime = [controller selectionEndTime];
644    ox1 = TIMEtoX(selectionStartTime);
645    ox2 = TIMEtoX(selectionEndTime);
646    nx1 = TIMEtoX(newSelectionStartTime);
647    nx2 = TIMEtoX(newSelectionEndTime);
648    if (newSelectionStartTime == nil || nx1 >= nx2) {
649        if (selectionExists) {
650            [self setNeedsDisplayFromX:ox1 toX:ox2];
651            Assign(selectionStartTime, nil);
652            Assign(selectionEndTime, nil);
653            selectionExists = NO;
654            [zoomToSelectionButton setEnabled:NO];
655        }
656        return;
657    }
658    if (!selectionExists) {
659        Assign(selectionStartTime, newSelectionStartTime);
660        Assign(selectionEndTime, newSelectionEndTime);
661        selectionExists = YES;
662        [zoomToSelectionButton setEnabled:YES];
663        [self setNeedsDisplayFromX:nx1 toX:nx2];
664        return;
665    }
666
667    if (ox2 < nx1 || nx2 < ox1) {
668        [self setNeedsDisplayFromX:ox1 toX:ox2];
669        Assign(selectionStartTime, newSelectionStartTime);
670        Assign(selectionEndTime, newSelectionEndTime);
671        [self setNeedsDisplayFromX:nx1 toX:nx2];
672        return;
673    }
674
675    redisplayRect = [self visibleRect];
676    if (nx1 != ox1) {
677        [self setNeedsDisplayFromX:MIN(nx1, ox1) toX:MAX(nx1, ox1)];
678    }
679    if (nx2 != ox2) {
680        [self setNeedsDisplayFromX:MIN(nx2, ox2) toX:MAX(nx2, ox2)];
681    }
682
683    Assign(selectionStartTime, newSelectionStartTime);
684    Assign(selectionEndTime, newSelectionEndTime);
685}
686
687- (void)changeSelectionWithPoint:(NSPoint)point
688{
689    NSDate *cursorTime;
690    NSRect frameRect = [self frame];
691
692    if (point.x < NSMinX(frameRect)) point.x = NSMinX(frameRect);
693    if (point.x > NSMaxX(frameRect)) point.x = NSMaxX(frameRect);
694
695    cursorTime = XtoTIME(point.x);
696    [self setCursorTime:cursorTime];
697
698    if ([cursorTime isEarlierThanDate:selectionAnchorTime]) {
699        [controller setSelectionStartTime:cursorTime
700                                  endTime:selectionAnchorTime];
701    } else {
702        [controller setSelectionStartTime:selectionAnchorTime
703                                  endTime:cursorTime];
704    }
705
706    // scroll point to visible
707    NSRect visibleRect;
708    visibleRect = [self visibleRect];
709    visibleRect.origin.x = point.x;
710    visibleRect.size.width = 1;
711    [self scrollRectToVisible:visibleRect];
712}
713
714- (void)setNeedsDisplayFromX:(double)x1 toX:(double)x2
715{
716    NSRect redisplayRect = [self visibleRect];
717    redisplayRect.origin.x = x1;
718    redisplayRect.size.width = x2 - x1;
719    redisplayRect = NSIntegralRect(redisplayRect);
720    [self setNeedsDisplayInRect:redisplayRect];
721}
722
723- (void)selectAll:(id)sender
724{
725    [controller setSelectionStartTime:startTime
726                              endTime:endTime];
727}
728
729
730
731/*
732 * Dragging control (NSDraggingDestination protocol)
733 * ------------------------------------------------------------------------
734 */
735
736- (unsigned int)draggingUpdated:(id <NSDraggingInfo>)sender
737{
738    NSPoint point = [self convertPoint:[sender draggingLocation] fromView:nil];
739
740    [self setCursorEntity:[self findEntityAtPoint:point]];
741#ifdef GNUSTEP
742return NSDragOperationCopy;
743#else
744//    if (cursorEntity)
745        return NSDragOperationAll;
746//    else
747//        return NSDragOperationNone;
748#endif
749}
750
751#ifdef GNUSTEP
752// this shouldn't be needed (should be the default behaviour of NSView)
753- (BOOL) prepareForDragOperation: (id <NSDraggingInfo>)sender
754{
755    return YES;
756}
757#endif
758- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
759{
760    NSColor *draggedColor;
761    NSPoint point = [self convertPoint:[sender draggingLocation] fromView:nil];
762
763    draggedColor = [NSColor colorFromPasteboard:[sender draggingPasteboard]];
764    if (draggedColor == nil) {
765        return NO;
766    }
767
768    // if some entity is under the cursor, the color has been dropped over it.
769    if (cursorEntity != nil) {
770        [filter setColor:draggedColor forEntity:cursorEntity];
771        [self setCursorEntity:nil];
772    } else {
773        // didn't drop on an entity, change background color
774        if ([self isPointInSelection:point]) {
775            [self setSelectedBackgroundColor:draggedColor];
776        } else {
777            [self setBackgroundColor:draggedColor];
778        }
779    }
780    return YES;
781}
782
783
784/*
785 * Printing
786 * ------------------------------------------------------------------------
787 */
788
789- (void)endPrologue
790/*
791 * Spit out the custom PostScript defs.
792 */
793{
794    [super endPrologue];
795}
796@end
797