#import "JVMarkedScroller.h" @implementation JVMarkedScroller - (id) initWithFrame:(NSRect) frame { if( ( self = [super initWithFrame:frame] ) ) { _marks = [[NSMutableSet set] retain]; _shades = [[NSMutableArray array] retain]; _lines = [[NSBezierPath bezierPath] retain]; [_lines setLineWidth:1.]; _shadedAreas = [[NSBezierPath bezierPath] retain]; } return self; } - (void) dealloc { [_marks release]; [_shades release]; [_lines release]; [_shadedAreas release]; _marks = nil; _shades = nil; _lines = nil; _shadedAreas = nil; [super dealloc]; } - (void) drawRect:(NSRect) rect { NSEraseRect( rect ); [super drawRect:rect]; NSAffineTransform *transform = [NSAffineTransform transform]; NSRect clip = NSInsetRect( [self rectForPart:NSScrollerKnobSlot], ( sFlags.isHoriz ? 6. : 0. ), ( sFlags.isHoriz ? 0. : 6. ) ); float scale = NSHeight( clip ) / ( NSHeight( [self frame] ) / [self knobProportion] ); [transform scaleXBy:( sFlags.isHoriz ? scale : 1. ) yBy:( sFlags.isHoriz ? 1. : scale )]; float offset = [self rectForPart:NSScrollerKnobSlot].origin.y + 6.; [transform translateXBy:( sFlags.isHoriz ? offset / scale : 0. ) yBy:( sFlags.isHoriz ? 0. : offset / scale )]; clip = NSInsetRect( [self rectForPart:NSScrollerKnobSlot], ( sFlags.isHoriz ? 0. : 3. ), ( sFlags.isHoriz ? 3. : 0. ) ); NSRectClip( clip ); NSBezierPath *shaded = _shadedAreas; if( ( [_shades count] % 2 ) == 1 ) { shaded = [[_shadedAreas copy] autorelease]; unsigned long long start = [[_shades lastObject] unsignedLongLongValue]; unsigned long long stop = ( NSHeight( [self frame] ) / [self knobProportion] ); float width = [[self class] scrollerWidthForControlSize:[self controlSize]]; NSRect rect = NSZeroRect; if( sFlags.isHoriz ) rect = NSMakeRect( start, 0., ( stop - start ), width ); else rect = NSMakeRect( 0., start, width, ( stop - start ) ); [shaded appendBezierPathWithRect:rect]; } [[[NSColor knobColor] colorWithAlphaComponent:0.45] set]; [[transform transformBezierPath:shaded] fill]; clip = NSInsetRect( [self rectForPart:NSScrollerKnobSlot], ( sFlags.isHoriz ? 4. : 3. ), ( sFlags.isHoriz ? 3. : 4. ) ); NSRectClip( clip ); [[NSColor selectedKnobColor] set]; [[transform transformBezierPath:_lines] stroke]; [self drawKnob]; } - (void) setFloatValue:(float) position knobProportion:(float) percent { [self setNeedsDisplayInRect:[self rectForPart:NSScrollerKnobSlot]]; [super setFloatValue:position knobProportion:percent]; } - (NSMenu *) menuForEvent:(NSEvent *) event { NSMenu *menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; NSMenuItem *item = nil; item = [[[NSMenuItem alloc] initWithTitle:NSLocalizedString( @"Clear All Marks", "clear all marks contextual menu item title" ) action:@selector( removeAllMarks ) keyEquivalent:@""] autorelease]; [item setTarget:self]; [menu addItem:item]; if( sFlags.isHoriz ) { item = [[[NSMenuItem alloc] initWithTitle:NSLocalizedString( @"Clear Marks from Here Left", "clear marks from here left contextual menu") action:@selector( clearMarksHereLess: ) keyEquivalent:@""] autorelease]; [item setTarget:self]; [menu addItem:item]; item = [[[NSMenuItem alloc] initWithTitle:NSLocalizedString( @"Clear Marks from Here Right", "clear marks from here right contextual menu") action:@selector( clearMarksHereGreater: ) keyEquivalent:@""] autorelease]; [item setTarget:self]; [menu addItem:item]; } else { item = [[[NSMenuItem alloc] initWithTitle:NSLocalizedString( @"Clear Marks from Here Up", "clear marks from here up contextual menu") action:@selector( clearMarksHereLess: ) keyEquivalent:@""] autorelease]; [item setTarget:self]; [menu addItem:item]; item = [[[NSMenuItem alloc] initWithTitle:NSLocalizedString( @"Clear Marks from Here Down", "clear marks from here up contextual menu") action:@selector( clearMarksHereGreater: ) keyEquivalent:@""] autorelease]; [item setTarget:self]; [menu addItem:item]; } return menu; } - (IBAction) clearMarksHereLess:(id) sender { NSEvent *event = [[NSApplication sharedApplication] currentEvent]; NSPoint where = [self convertPoint:[event locationInWindow] fromView:nil]; NSRect clip = NSInsetRect( [self rectForPart:NSScrollerKnobSlot], ( sFlags.isHoriz ? 6. : 0. ), ( sFlags.isHoriz ? 0. : 6. ) ); float scale = NSHeight( clip ) / ( NSHeight( [self frame] ) / [self knobProportion] ); [self removeMarksLessThan:( ( sFlags.isHoriz ? where.x : where.y ) / scale )]; } - (IBAction) clearMarksHereGreater:(id) sender { NSEvent *event = [[NSApplication sharedApplication] currentEvent]; NSPoint where = [self convertPoint:[event locationInWindow] fromView:nil]; NSRect clip = NSInsetRect( [self rectForPart:NSScrollerKnobSlot], ( sFlags.isHoriz ? 6. : 0. ), ( sFlags.isHoriz ? 0. : 6. ) ); float scale = NSHeight( clip ) / ( NSHeight( [self frame] ) / [self knobProportion] ); [self removeMarksGreaterThan:( ( sFlags.isHoriz ? where.x : where.y ) / scale )]; } #pragma mark - - (void) shiftMarksAndShadedAreasBy:(long long) displacement { NSAffineTransform *transform = [NSAffineTransform transform]; [transform translateXBy:(float)( sFlags.isHoriz ? displacement : 0. ) yBy:(float)( sFlags.isHoriz ? 0. : displacement )]; NSMutableSet *shiftedMarks = [NSMutableSet set]; NSEnumerator *enumerator = [_marks objectEnumerator]; NSNumber *location = nil; while( ( location = [enumerator nextObject] ) ) { long long shifted = ( [location unsignedLongLongValue] + displacement ); if( shifted >= 0 ) [shiftedMarks addObject:[NSNumber numberWithUnsignedLongLong:shifted]]; } [_lines transformUsingAffineTransform:transform]; [_marks setSet:shiftedMarks]; NSMutableArray *shiftedShades = [NSMutableArray array]; enumerator = [_shades objectEnumerator]; location = nil; while( ( location = [enumerator nextObject] ) ) { long long shifted = ( [location unsignedLongLongValue] + displacement ); [shiftedShades addObject:[NSNumber numberWithUnsignedLongLong:MAX( shifted, 0 )]]; } [_shadedAreas transformUsingAffineTransform:transform]; [_shades setArray:shiftedShades]; [self setNeedsDisplayInRect:[self rectForPart:NSScrollerKnobSlot]]; } #pragma mark - - (void) buildLineAtLocation:(unsigned long long) location { float width = [[self class] scrollerWidthForControlSize:[self controlSize]]; [_lines moveToPoint:NSMakePoint( ( sFlags.isHoriz ? location : 0. ), ( sFlags.isHoriz ? 0. : location ) )]; [_lines relativeLineToPoint:NSMakePoint( ( sFlags.isHoriz ? 0. : width ), ( sFlags.isHoriz ? width : 0. ) )]; } - (void) rebuildLines { NSEnumerator *enumerator = [_marks objectEnumerator]; NSNumber *location = nil; [_lines removeAllPoints]; while( ( location = [enumerator nextObject] ) ) [self buildLineAtLocation:[location unsignedLongLongValue]]; } #pragma mark - - (void) addMarkAt:(unsigned long long) location { [_marks addObject:[NSNumber numberWithUnsignedLongLong:location]]; [self buildLineAtLocation:location]; [self setNeedsDisplayInRect:[self rectForPart:NSScrollerKnobSlot]]; } - (void) removeMarkAt:(unsigned long long) location { [_marks removeObject:[NSNumber numberWithUnsignedLongLong:location]]; [self rebuildLines]; [self setNeedsDisplayInRect:[self rectForPart:NSScrollerKnobSlot]]; } - (void) removeMarksGreaterThan:(unsigned long long) location { NSEnumerator *enumerator = [[[_marks copy] autorelease] objectEnumerator]; NSNumber *number = nil; while( ( number = [enumerator nextObject] ) ) if( [number unsignedIntValue] > location ) [_marks removeObject:number]; [self rebuildLines]; [self setNeedsDisplayInRect:[self rectForPart:NSScrollerKnobSlot]]; } - (void) removeMarksLessThan:(unsigned long long) location { NSEnumerator *enumerator = [[[_marks copy] autorelease] objectEnumerator]; NSNumber *number = nil; while( ( number = [enumerator nextObject] ) ) if( [number unsignedIntValue] < location ) [_marks removeObject:number]; [self rebuildLines]; [self setNeedsDisplayInRect:[self rectForPart:NSScrollerKnobSlot]]; } - (void) removeMarksInRange:(NSRange) range { NSEnumerator *enumerator = [[[_marks copy] autorelease] objectEnumerator]; NSNumber *number = nil; while( ( number = [enumerator nextObject] ) ) if( NSLocationInRange( [number unsignedIntValue], range ) ) [_marks removeObject:number]; [self rebuildLines]; [self setNeedsDisplayInRect:[self rectForPart:NSScrollerKnobSlot]]; } - (void) removeAllMarks { [_marks removeAllObjects]; [_lines removeAllPoints]; [self setNeedsDisplayInRect:[self rectForPart:NSScrollerKnobSlot]]; } #pragma mark - - (void) setMarks:(NSSet *) marks { [_marks autorelease]; _marks = [[NSMutableSet setWithSet:marks] retain]; [self rebuildLines]; } - (NSSet *) marks { return [[_marks retain] autorelease]; } #pragma mark - - (void) buildShadedAreaBetween:(unsigned long long) start and:(unsigned long long) stop { NSRect rect = NSZeroRect; float width = [[self class] scrollerWidthForControlSize:[self controlSize]]; if( sFlags.isHoriz ) rect = NSMakeRect( start, 0., ( stop - start ), width ); else rect = NSMakeRect( 0., start, width, ( stop - start ) ); [_shadedAreas appendBezierPathWithRect:rect]; } - (void) rebuildShadedAreas { NSEnumerator *enumerator = [_marks objectEnumerator]; NSNumber *start = nil; NSNumber *stop = nil; [_shadedAreas removeAllPoints]; while( ( start = [enumerator nextObject] ) && ( stop = [enumerator nextObject] ) ) [self buildShadedAreaBetween:[start unsignedLongLongValue] and:[stop unsignedLongLongValue]]; } #pragma mark - - (void) startShadedAreaAt:(unsigned long long) location { if( ! [_shades count] || ! ( [_shades count] % 2 ) ) { [_shades addObject:[NSNumber numberWithUnsignedLongLong:location]]; [self setNeedsDisplayInRect:[self rectForPart:NSScrollerKnobSlot]]; } } - (void) stopShadedAreaAt:(unsigned long long) location { if( [_shades count] && ( [_shades count] % 2 ) == 1 ) { NSNumber *start = [_shades lastObject]; [_shades addObject:[NSNumber numberWithUnsignedLongLong:location]]; [self buildShadedAreaBetween:[start unsignedLongLongValue] and:location]; [self setNeedsDisplayInRect:[self rectForPart:NSScrollerKnobSlot]]; } } #pragma mark - - (void) removeAllShadedAreas { [_shades removeAllObjects]; [_shadedAreas removeAllPoints]; [self setNeedsDisplayInRect:[self rectForPart:NSScrollerKnobSlot]]; } @end