00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 @import "CPControl.j"
00024
00025 #include "CoreGraphics/CGGeometry.h"
00026
00027
00028
00029 CPScrollerNoPart = 0;
00030 CPScrollerDecrementPage = 1;
00031 CPScrollerKnob = 2;
00032 CPScrollerIncrementPage = 3;
00033 CPScrollerDecrementLine = 4;
00034 CPScrollerIncrementLine = 5;
00035 CPScrollerKnobSlot = 6;
00036
00037 CPScrollerIncrementArrow = 0;
00038 CPScrollerDecrementArrow = 1;
00039
00040 CPNoScrollerParts = 0;
00041 CPOnlyScrollerArrows = 1;
00042 CPAllScrollerParts = 2;
00043
00049 var PARTS_ARRANGEMENT = [CPScrollerKnobSlot, CPScrollerDecrementLine, CPScrollerIncrementLine, CPScrollerKnob],
00050 NAMES_FOR_PARTS = {},
00051 PARTS_FOR_NAMES = {};
00052
00053 NAMES_FOR_PARTS[CPScrollerDecrementLine] = @"decrement-line";
00054 NAMES_FOR_PARTS[CPScrollerIncrementLine] = @"increment-line";
00055 NAMES_FOR_PARTS[CPScrollerKnobSlot] = @"knob-slot";
00056 NAMES_FOR_PARTS[CPScrollerKnob] = @"knob";
00057
00058
00059 @implementation CPScroller : CPControl
00060 {
00061 CPControlSize _controlSize;
00062 CPUsableScrollerParts _usableParts;
00063 CPArray _partRects;
00064
00065 BOOL _isVertical @accessors(readonly, getter=isVertical);
00066 float _knobProportion;
00067
00068 CPScrollerPart _hitPart;
00069
00070 CPScrollerPart _trackingPart;
00071 float _trackingFloatValue;
00072 CGPoint _trackingStartPoint;
00073 }
00074
00075 + (CPString)themeClass
00076 {
00077 return "scroller";
00078 }
00079
00080 + (id)themeAttributes
00081 {
00082 return [CPDictionary dictionaryWithObjects:[ [CPNull null], [CPNull null], [CPNull null], [CPNull null],
00083 _CGSizeMakeZero(), _CGSizeMakeZero(), _CGInsetMakeZero(), _CGInsetMakeZero(), _CGSizeMakeZero()]
00084 forKeys:[ @"knob-slot-color",
00085 @"decrement-line-color",
00086 @"increment-line-color",
00087 @"knob-color",
00088 @"decrement-line-size",
00089 @"increment-line-size",
00090 @"track-inset",
00091 @"knob-inset",
00092 @"minimum-knob-length"]];
00093 }
00094
00095
00096
00097
00098 - (id)initWithFrame:(CGRect)aFrame
00099 {
00100 self = [super initWithFrame:aFrame];
00101
00102 if (self)
00103 {
00104 _controlSize = CPRegularControlSize;
00105 _partRects = [];
00106
00107 [self setFloatValue:0.0];
00108 [self setKnobProportion:1.0];
00109
00110 _hitPart = CPScrollerNoPart;
00111
00112 [self _calculateIsVertical];
00113 }
00114
00115 return self;
00116 }
00117
00118
00122 + (float)scrollerWidth
00123 {
00124 return 15.0;
00125 }
00126
00131 + (float)scrollerWidthForControlSize:(CPControlSize)aControlSize
00132 {
00133 return 15.0;
00134 }
00135
00140 - (void)setControlSize:(CPControlSize)aControlSize
00141 {
00142 if (_controlSize == aControlSize)
00143 return;
00144
00145 _controlSize = aControlSize;
00146
00147 [self setNeedsLayout];
00148 [self setNeedsDisplay:YES];
00149 }
00150
00154 - (CPControlSize)controlSize
00155 {
00156 return _controlSize;
00157 }
00158
00159 - (void)setObjectValue:(id)aValue
00160 {
00161 [super setObjectValue:MIN(1.0, MAX(0.0, +aValue))];
00162 }
00163
00164 - (void)setKnobProportion:(float)aProportion
00165 {
00166 _knobProportion = MIN(1.0, MAX(0.0001, aProportion));
00167
00168 [self setNeedsDisplay:YES];
00169 [self setNeedsLayout];
00170 }
00171
00175 - (float)knobProportion
00176 {
00177 return _knobProportion;
00178 }
00179
00180 - (id)currentValueForThemeAttribute:(CPString)anAttributeName
00181 {
00182 var themeState = _themeState;
00183
00184 if (NAMES_FOR_PARTS[_hitPart] + "-color" !== anAttributeName)
00185 themeState &= ~CPThemeStateHighlighted;
00186
00187 return [self valueForThemeAttribute:anAttributeName inState:themeState];
00188 }
00189
00190
00191
00192 - (CGRect)rectForPart:(CPScrollerPart)aPart
00193 {
00194 if (aPart == CPScrollerNoPart)
00195 return _CGRectMakeZero();
00196
00197 return _partRects[aPart];
00198 }
00199
00205 - (CPScrollerPart)testPart:(CGPoint)aPoint
00206 {
00207 aPoint = [self convertPoint:aPoint fromView:nil];
00208
00209
00210
00211
00212 if (CGRectContainsPoint([self rectForPart:CPScrollerKnob], aPoint))
00213 return CPScrollerKnob;
00214
00215 if (CGRectContainsPoint([self rectForPart:CPScrollerDecrementPage], aPoint))
00216 return CPScrollerDecrementPage;
00217
00218 if (CGRectContainsPoint([self rectForPart:CPScrollerIncrementPage], aPoint))
00219 return CPScrollerIncrementPage;
00220
00221 if (CGRectContainsPoint([self rectForPart:CPScrollerDecrementLine], aPoint))
00222 return CPScrollerDecrementLine;
00223
00224 if (CGRectContainsPoint([self rectForPart:CPScrollerIncrementLine], aPoint))
00225 return CPScrollerIncrementLine;
00226
00227 if (CGRectContainsPoint([self rectForPart:CPScrollerKnobSlot], aPoint))
00228 return CPScrollerKnobSlot;
00229
00230 return CPScrollerNoPart;
00231 }
00232
00236 - (void)checkSpaceForParts
00237 {
00238 var bounds = [self bounds];
00239
00240
00241 if (_knobProportion === 1.0)
00242 {
00243 _usableParts = CPNoScrollerParts;
00244
00245 _partRects[CPScrollerDecrementPage] = CGRectMakeZero();
00246 _partRects[CPScrollerKnob] = CGRectMakeZero();
00247 _partRects[CPScrollerIncrementPage] = CGRectMakeZero();
00248 _partRects[CPScrollerDecrementLine] = CGRectMakeZero();
00249 _partRects[CPScrollerIncrementLine] = CGRectMakeZero();
00250
00251
00252 _partRects[CPScrollerKnobSlot] = CGRectMakeCopy(bounds);
00253
00254 return;
00255 }
00256
00257
00258 _usableParts = CPAllScrollerParts;
00259
00260 var knobInset = [self currentValueForThemeAttribute:@"knob-inset"],
00261 trackInset = [self currentValueForThemeAttribute:@"track-inset"],
00262 width = _CGRectGetWidth(bounds),
00263 height = _CGRectGetHeight(bounds);
00264
00265 if ([self isVertical])
00266 {
00267 var decrementLineSize = [self currentValueForThemeAttribute:"decrement-line-size"],
00268 incrementLineSize = [self currentValueForThemeAttribute:"increment-line-size"],
00269 effectiveDecrementLineHeight = decrementLineSize.height + trackInset.top,
00270 effectiveIncrementLineHeight = incrementLineSize.height + trackInset.bottom,
00271 slotHeight = height - effectiveDecrementLineHeight - effectiveIncrementLineHeight,
00272 minimumKnobLength = [self currentValueForThemeAttribute:"minimum-knob-length"],
00273 knobWidth = width - knobInset.left - knobInset.right,
00274 knobHeight = MAX(minimumKnobLength, (slotHeight * _knobProportion)),
00275 knobLocation = effectiveDecrementLineHeight + (slotHeight - knobHeight) * [self floatValue];
00276
00277 _partRects[CPScrollerDecrementPage] = _CGRectMake(0.0, effectiveDecrementLineHeight, width, knobLocation - effectiveDecrementLineHeight);
00278 _partRects[CPScrollerKnob] = _CGRectMake(knobInset.left, knobLocation, knobWidth, knobHeight);
00279 _partRects[CPScrollerIncrementPage] = _CGRectMake(0.0, knobLocation + knobHeight, width, height - (knobLocation + knobHeight) - effectiveIncrementLineHeight);
00280 _partRects[CPScrollerKnobSlot] = _CGRectMake(trackInset.left, effectiveDecrementLineHeight, width - trackInset.left - trackInset.right, slotHeight);
00281 _partRects[CPScrollerDecrementLine] = _CGRectMake(0.0, 0.0, decrementLineSize.width, decrementLineSize.height);
00282 _partRects[CPScrollerIncrementLine] = _CGRectMake(0.0, height - incrementLineSize.height, incrementLineSize.width, incrementLineSize.height);
00283
00284 if(height < knobHeight + decrementLineSize.height + incrementLineSize.height + trackInset.top + trackInset.bottom)
00285 _partRects[CPScrollerKnob] = _CGRectMakeZero();
00286
00287 if(height < decrementLineSize.height + incrementLineSize.height - 2)
00288 {
00289 _partRects[CPScrollerIncrementLine] = _CGRectMakeZero();
00290 _partRects[CPScrollerDecrementLine] = _CGRectMakeZero();
00291 _partRects[CPScrollerKnobSlot] = _CGRectMake(trackInset.left, 0, width - trackInset.left - trackInset.right, height);
00292 }
00293 }
00294 else
00295 {
00296 var decrementLineSize = [self currentValueForThemeAttribute:"decrement-line-size"],
00297 incrementLineSize = [self currentValueForThemeAttribute:"increment-line-size"],
00298 effectiveDecrementLineWidth = decrementLineSize.width + trackInset.left,
00299 effectiveIncrementLineWidth = incrementLineSize.width + trackInset.right;
00300 slotWidth = width - effectiveDecrementLineWidth - effectiveIncrementLineWidth,
00301 minimumKnobLength = [self currentValueForThemeAttribute:"minimum-knob-length"],
00302 knobWidth = MAX(minimumKnobLength, (slotWidth * _knobProportion)),
00303 knobHeight = height - knobInset.top - knobInset.bottom,
00304 knobLocation = effectiveDecrementLineWidth + (slotWidth - knobWidth) * [self floatValue];
00305
00306 _partRects[CPScrollerDecrementPage] = _CGRectMake(effectiveDecrementLineWidth, 0.0, knobLocation - effectiveDecrementLineWidth, height);
00307 _partRects[CPScrollerKnob] = _CGRectMake(knobLocation, knobInset.top, knobWidth, knobHeight);
00308 _partRects[CPScrollerIncrementPage] = _CGRectMake(knobLocation + knobWidth, 0.0, width - (knobLocation + knobWidth) - effectiveIncrementLineWidth, height);
00309 _partRects[CPScrollerKnobSlot] = _CGRectMake(effectiveDecrementLineWidth, trackInset.top, slotWidth, height - trackInset.top - trackInset.bottom);
00310 _partRects[CPScrollerDecrementLine] = _CGRectMake(0.0, 0.0, decrementLineSize.width, decrementLineSize.height);
00311 _partRects[CPScrollerIncrementLine] = _CGRectMake(width - incrementLineSize.width, 0.0, incrementLineSize.width, incrementLineSize.height);
00312
00313 if(width < knobWidth + decrementLineSize.width + incrementLineSize.width + trackInset.left + trackInset.right)
00314 _partRects[CPScrollerKnob] = _CGRectMakeZero();
00315
00316 if(width < decrementLineSize.width + incrementLineSize.width - 2)
00317 {
00318 _partRects[CPScrollerIncrementLine] = _CGRectMakeZero();
00319 _partRects[CPScrollerDecrementLine] = _CGRectMakeZero();
00320 _partRects[CPScrollerKnobSlot] = _CGRectMake(0.0, 0.0, width, slotHeight);
00321 }
00322 }
00323 }
00324
00329 - (CPUsableScrollerParts)usableParts
00330 {
00331 return _usableParts;
00332 }
00333
00334
00340 - (void)drawArrow:(CPScrollerArrow)anArrow highlight:(BOOL)shouldHighlight
00341 {
00342 }
00343
00347 - (void)drawKnob
00348 {
00349 }
00350
00354 - (void)drawKnobSlot
00355 {
00356 }
00357
00358 - (CPView)createViewForPart:(CPScrollerPart)aPart
00359 {
00360 var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
00361
00362 [view setHitTests:NO];
00363
00364 return view;
00365 }
00366
00367 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
00368 {
00369 return _partRects[aName];
00370 }
00371
00372 - (CPView)createEphemeralSubviewNamed:(CPString)aName
00373 {
00374 var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
00375
00376 [view setHitTests:NO];
00377
00378 return view;
00379 }
00380
00381 - (void)layoutSubviews
00382 {
00383 [self checkSpaceForParts];
00384
00385 var index = 0,
00386 count = PARTS_ARRANGEMENT.length;
00387
00388 for (; index < count; ++index)
00389 {
00390 var part = PARTS_ARRANGEMENT[index];
00391
00392 if (index === 0)
00393 view = [self layoutEphemeralSubviewNamed:part positioned:CPWindowBelow relativeToEphemeralSubviewNamed:PARTS_ARRANGEMENT[index + 1]];
00394 else
00395 view = [self layoutEphemeralSubviewNamed:part positioned:CPWindowAbove relativeToEphemeralSubviewNamed:PARTS_ARRANGEMENT[index - 1]];
00396
00397 if (view)
00398 [view setBackgroundColor:[self currentValueForThemeAttribute:NAMES_FOR_PARTS[part] + "-color"]];
00399 }
00400 }
00401
00405 - (void)drawParts
00406 {
00407 [self drawKnobSlot];
00408 [self drawKnob];
00409 [self drawArrow:CPScrollerDecrementArrow highlight:NO];
00410 [self drawArrow:CPScrollerIncrementArrow highlight:NO];
00411 }
00412
00413
00417 - (CPScrollerPart)hitPart
00418 {
00419 return _hitPart;
00420 }
00421
00426 - (void)trackKnob:(CPEvent)anEvent
00427 {
00428 var type = [anEvent type];
00429
00430 if (type === CPLeftMouseUp)
00431 {
00432 _hitPart = CPScrollerNoPart;
00433
00434 return;
00435 }
00436
00437 if (type === CPLeftMouseDown)
00438 {
00439 _trackingFloatValue = [self floatValue];
00440 _trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
00441 }
00442
00443 else if (type === CPLeftMouseDragged)
00444 {
00445 var knobRect = [self rectForPart:CPScrollerKnob],
00446 knobSlotRect = [self rectForPart:CPScrollerKnobSlot],
00447 remainder = ![self isVertical] ? (_CGRectGetWidth(knobSlotRect) - _CGRectGetWidth(knobRect)) : (_CGRectGetHeight(knobSlotRect) - _CGRectGetHeight(knobRect));
00448
00449 if (remainder <= 0)
00450 [self setFloatValue:0.0];
00451 else
00452 {
00453 var location = [self convertPoint:[anEvent locationInWindow] fromView:nil];
00454 delta = ![self isVertical] ? location.x - _trackingStartPoint.x : location.y - _trackingStartPoint.y;
00455
00456 [self setFloatValue:_trackingFloatValue + delta / remainder];
00457 }
00458 }
00459
00460 [CPApp setTarget:self selector:@selector(trackKnob:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
00461
00462 [self sendAction:[self action] to:[self target]];
00463 }
00464
00469 - (void)trackScrollButtons:(CPEvent)anEvent
00470 {
00471 var type = [anEvent type];
00472
00473 if (type === CPLeftMouseUp)
00474 {
00475 [self highlight:NO];
00476 [CPEvent stopPeriodicEvents];
00477
00478 _hitPart = CPScrollerNoPart;
00479
00480 return;
00481 }
00482
00483 if (type === CPLeftMouseDown)
00484 {
00485 _trackingPart = [self hitPart];
00486
00487 _trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
00488
00489 if ([anEvent modifierFlags] & CPAlternateKeyMask)
00490 {
00491 if (_trackingPart == CPScrollerDecrementLine)
00492 _hitPart = CPScrollerDecrementPage;
00493
00494 else if (_trackingPart == CPScrollerIncrementLine)
00495 _hitPart = CPScrollerIncrementPage;
00496
00497 else if (_trackingPart == CPScrollerDecrementPage || _trackingPart == CPScrollerIncrementPage)
00498 {
00499 var knobRect = [self rectForPart:CPScrollerKnob],
00500 knobWidth = ![self isVertical] ? _CGRectGetWidth(knobRect) : _CGRectGetHeight(knobRect),
00501 knobSlotRect = [self rectForPart:CPScrollerKnobSlot],
00502 remainder = (![self isVertical] ? _CGRectGetWidth(knobSlotRect) : _CGRectGetHeight(knobSlotRect)) - knobWidth;
00503
00504 [self setFloatValue:((![self isVertical] ? _trackingStartPoint.x - _CGRectGetMinX(knobSlotRect) : _trackingStartPoint.y - _CGRectGetMinY(knobSlotRect)) - knobWidth / 2.0) / remainder];
00505
00506 _hitPart = CPScrollerKnob;
00507
00508 [self sendAction:[self action] to:[self target]];
00509
00510
00511 return [self trackKnob:anEvent];
00512 }
00513 }
00514
00515 [self highlight:YES];
00516 [self sendAction:[self action] to:[self target]];
00517
00518 [CPEvent startPeriodicEventsAfterDelay:0.5 withPeriod:0.04];
00519 }
00520
00521 else if (type === CPLeftMouseDragged)
00522 {
00523 _trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
00524
00525 if (_trackingPart == CPScrollerDecrementPage || _trackingPart == CPScrollerIncrementPage)
00526 {
00527 var hitPart = [self testPart:[anEvent locationInWindow]];
00528
00529 if (hitPart == CPScrollerDecrementPage || hitPart == CPScrollerIncrementPage)
00530 {
00531 _trackingPart = hitPart;
00532 _hitPart = hitPart;
00533 }
00534 }
00535
00536 [self highlight:CGRectContainsPoint([self rectForPart:_trackingPart], _trackingStartPoint)];
00537 }
00538 else if (type == CPPeriodic && CGRectContainsPoint([self rectForPart:_trackingPart], _trackingStartPoint))
00539 [self sendAction:[self action] to:[self target]];
00540
00541 [CPApp setTarget:self selector:@selector(trackScrollButtons:) forNextEventMatchingMask:CPPeriodicMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
00542
00543 }
00544
00545 - (void)_calculateIsVertical
00546 {
00547
00548 var bounds = [self bounds],
00549 width = _CGRectGetWidth(bounds),
00550 height = _CGRectGetHeight(bounds);
00551
00552 _isVertical = width < height ? 1 : (width > height ? 0 : -1);
00553
00554 if (_isVertical === 1)
00555 [self setThemeState:CPThemeStateVertical];
00556 else if (_isVertical === 0)
00557 [self unsetThemeState:CPThemeStateVertical];
00558 }
00559
00560 - (void)setFrameSize:(CGSize)aSize
00561 {
00562 [super setFrameSize:aSize];
00563
00564 [self checkSpaceForParts];
00565 [self setNeedsLayout];
00566 }
00567
00568 - (void)mouseDown:(CPEvent)anEvent
00569 {
00570 if (![self isEnabled])
00571 return;
00572
00573 _hitPart = [self testPart:[anEvent locationInWindow]];
00574
00575 switch (_hitPart)
00576 {
00577 case CPScrollerKnob: return [self trackKnob:anEvent];
00578
00579 case CPScrollerDecrementLine:
00580 case CPScrollerIncrementLine:
00581 case CPScrollerDecrementPage:
00582 case CPScrollerIncrementPage: return [self trackScrollButtons:anEvent];
00583 }
00584 }
00585
00586 @end
00587
00588 var CPScrollerControlSizeKey = "CPScrollerControlSize",
00589 CPScrollerKnobProportionKey = "CPScrollerKnobProportion";
00590
00591 @implementation CPScroller (CPCoding)
00592
00593 - (id)initWithCoder:(CPCoder)aCoder
00594 {
00595 if (self = [super initWithCoder:aCoder])
00596 {
00597 _controlSize = CPRegularControlSize;
00598 if ([aCoder containsValueForKey:CPScrollerControlSizeKey])
00599 _controlSize = [aCoder decodeIntForKey:CPScrollerControlSizeKey];
00600
00601 _knobProportion = 1.0;
00602 if ([aCoder containsValueForKey:CPScrollerKnobProportionKey])
00603 _knobProportion = [aCoder decodeFloatForKey:CPScrollerKnobProportionKey];
00604
00605 _partRects = [];
00606
00607 _hitPart = CPScrollerNoPart;
00608
00609 [self _calculateIsVertical];
00610 }
00611
00612 return self;
00613 }
00614
00615 - (void)encodeWithCoder:(CPCoder)aCoder
00616 {
00617 [super encodeWithCoder:aCoder];
00618
00619 [aCoder encodeInt:_controlSize forKey:CPScrollerControlSizeKey];
00620 [aCoder encodeFloat:_knobProportion forKey:CPScrollerKnobProportionKey];
00621 }
00622
00623 @end
00624
00625 @implementation CPScroller (Deprecated)
00626
00632 - (void)setFloatValue:(float)aValue knobProportion:(float)aProportion
00633 {
00634 [self setFloatValue:aValue];
00635 [self setKnobProportion:aProportion];
00636 }
00637
00638 @end