diff --git a/AppKit/CPDatePicker/CPDatePicker.j b/AppKit/CPDatePicker/CPDatePicker.j index e246dabde..7fb715b9c 100644 --- a/AppKit/CPDatePicker/CPDatePicker.j +++ b/AppKit/CPDatePicker/CPDatePicker.j @@ -1,7 +1,7 @@ /* CPDatePicker.j * AppKit * -* Created by Alexandre Wilhelm +* Created by Alexandre Wilhelm * Copyright 2012 * * This library is free software; you can redistribute it and/or @@ -59,26 +59,26 @@ CPEraDatePickerElementFlag = 0x0100; */ @implementation CPDatePicker : CPControl { - BOOL _isBordered @accessors(getter=isBordered, setter=setBordered:); - BOOL _isBezeled @accessors(getter=isBezeled, setter=setBezeled:); - BOOL _drawsBackground @accessors(property=drawsBackground); - CPDate _dateValue @accessors(property=dateValue); - CPDate _minDate @accessors(property=minDate); - CPDate _maxDate @accessors(property=maxDate); - CPFont _textFont @accessors(property=textFont); - CPLocale _locale @accessors(property=locale); - //CPCalendar _calendar @accessors(property=calendar); - CPTimeZone _timeZone @accessors(property=timeZone); - id _delegate @accessors(property=delegate); - CPInteger _datePickerElements @accessors(property=datePickerElements); - CPInteger _datePickerMode @accessors(property=datePickerMode); - CPInteger _datePickerStyle @accessors(property=datePickerStyle); - CPInteger _timeInterval @accessors(property=timeInterval); + BOOL _isBordered @accessors(getter=isBordered, setter=setBordered:); + BOOL _isBezeled @accessors(getter=isBezeled, setter=setBezeled:); + BOOL _drawsBackground @accessors(property=drawsBackground); + CPDate _dateValue @accessors(property=dateValue); + CPDate _minDate @accessors(property=minDate); + CPDate _maxDate @accessors(property=maxDate); + CPFont _textFont @accessors(property=textFont); + CPLocale _locale @accessors(property=locale); + //CPCalendar _calendar @accessors(property=calendar); + CPTimeZone _timeZone @accessors(property=timeZone); + id _delegate @accessors(property=delegate); + CPInteger _datePickerElements @accessors(property=datePickerElements); + CPInteger _datePickerMode @accessors(property=datePickerMode); + CPInteger _datePickerStyle @accessors(property=datePickerStyle); + CPInteger _timeInterval @accessors(property=timeInterval); BOOL _invokedByUserEvent; - _CPDatePickerTextField _datePickerTextfield; - _CPDatePickerCalendar _datePickerCalendar; unsigned _implementedCDatePickerDelegateMethods; + BOOL _isTextual; + id _datePickerComponent; } @@ -144,6 +144,32 @@ CPEraDatePickerElementFlag = 0x0100; @"hour-hand-size": CGSizeMakeZero(), @"middle-hand-size": CGSizeMakeZero(), @"minute-hand-size": CGSizeMakeZero(), + @"previous-button-size": CGSizeMakeZero(), + @"current-button-size": CGSizeMakeZero(), + @"next-button-size": CGSizeMakeZero(), + @"title-inset": [CPNull null], + @"day-label-inset": [CPNull null], + @"tile-content-inset": CGInsetMakeZero(), + @"tile-margin": [CPNull null], + @"tile-inset": CGInsetMakeZero(), + @"separator-color": [CPNull null], + @"separator-margin-width": 0, + @"separator-height": 0, + @"bezel-color-calendar-left": [CPNull null], + @"bezel-color-calendar-middle": [CPNull null], + @"bezel-color-calendar-right": [CPNull null], + @"tile-vertical-alignment": CPCenterVerticalTextAlignment, + @"tile-text-alignment": CPCenterTextAlignment, + @"hour-ampm-margin": 2, + @"time-separator-content-inset": CGInsetMakeZero(), + @"clock-second-hand-over": NO, + @"clock-draws-hours": NO, + @"clock-hours-font": [CPNull null], + @"clock-hours-text-color": [CPColor clearColor], + @"clock-hours-radius": 0, + @"calendar-clock-margin": 10, + @"clock-only-nib2cib-adjustment-frame": CPRectMakeZero(), + @"uses-focus-ring": NO }; } @@ -206,19 +232,44 @@ CPEraDatePickerElementFlag = 0x0100; if (!_locale) _locale = [CPLocale currentLocale]; - _datePickerTextfield = [[_CPDatePickerTextField alloc] initWithFrame:[self bounds] withDatePicker:self]; - [_datePickerTextfield setDateValue:_dateValue]; + _datePickerComponent = nil; - _datePickerCalendar = [[_CPDatePickerCalendar alloc] initWithFrame:[self bounds] withDatePicker:self]; - [_datePickerCalendar setDateValue:_dateValue]; - - // We might have been unarchived in a disabled state. - [_datePickerTextfield setEnabled:[self isEnabled]]; + [self _createComponents]; [self setNeedsDisplay:YES]; [self setNeedsLayout]; } +- (void)_createComponents +{ + _isTextual = (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle) || (_datePickerStyle == CPTextFieldDatePickerStyle); + + if (_datePickerComponent) + { + [_datePickerComponent removeFromSuperview]; + + _datePickerComponent = nil; + } + + _datePickerComponent = [[(_isTextual ? _CPDatePickerTextField : _CPDatePickerCalendar) alloc] initWithFrame:[self bounds] withDatePicker:self]; + + [_datePickerComponent setDateValue:_dateValue]; + [_datePickerComponent setControlSize:[self controlSize]]; + [_datePickerComponent setDatePickerElements:_datePickerElements]; + + // FIXME: Don't know why but next line will cause theme compilation to fail... + // Workaround: added "if PLATFORM(DOM)" +#if PLATFORM(DOM) + [_datePickerComponent setEnabled:[self isEnabled]]; +#endif + + if (_isTextual) + // We need to transmit text color to the text field version (Cocoa doesn't permit adapting the calendar view text color) + [_datePickerComponent setTextColor:[self textColor]]; + + [self addSubview:_datePickerComponent]; +} + #pragma mark - #pragma mark Control Size @@ -227,7 +278,9 @@ CPEraDatePickerElementFlag = 0x0100; { [super setControlSize:aControlSize]; - if ([self datePickerStyle] == CPTextFieldAndStepperDatePickerStyle || [self datePickerStyle] == CPTextFieldDatePickerStyle) + [_datePickerComponent setControlSize:aControlSize]; + + if (_isTextual) [self _sizeToControlSize]; } @@ -256,31 +309,8 @@ CPEraDatePickerElementFlag = 0x0100; */ - (void)layoutSubviews { - [super layoutSubviews]; - - if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle) - { - if (![_datePickerTextfield superview]) - [self addSubview:_datePickerTextfield]; - - if ([_datePickerCalendar superview]) - [_datePickerCalendar removeFromSuperview]; - - [_datePickerTextfield setControlSize:[self controlSize]]; - [_datePickerTextfield setNeedsLayout]; - [_datePickerTextfield setNeedsDisplay:YES]; - } - else - { - if (![_datePickerCalendar superview]) - [self addSubview:_datePickerCalendar]; - - if ([_datePickerTextfield superview]) - [_datePickerTextfield removeFromSuperview]; - - [_datePickerCalendar setNeedsLayout]; - [_datePickerCalendar setNeedsDisplay:YES]; - } + [_datePickerComponent setNeedsLayout]; + [_datePickerComponent setNeedsDisplay:YES]; } #pragma mark - @@ -341,10 +371,7 @@ CPEraDatePickerElementFlag = 0x0100; if ([aDateValue isEqualToDate:_dateValue] && aTimeInterval == _timeInterval) { - if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle) - [_datePickerTextfield setDateValue:_dateValue]; - else - [_datePickerCalendar setDateValue:_dateValue]; + [_datePickerComponent setDateValue:_dateValue]; return; } @@ -372,10 +399,7 @@ CPEraDatePickerElementFlag = 0x0100; if (_invokedByUserEvent) [self sendAction:[self action] to:[self target]]; - if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle) - [_datePickerTextfield setDateValue:_dateValue]; - else - [_datePickerCalendar setDateValue:_dateValue]; + [_datePickerComponent setDateValue:_dateValue]; } /*! Set the minDate of the datePicker @@ -383,6 +407,9 @@ CPEraDatePickerElementFlag = 0x0100; */ - (void)setMinDate:(CPDate)aMinDate { + if (_minDate === aMinDate) + return; + [self willChangeValueForKey:@"minDate"]; _minDate = aMinDate; [self didChangeValueForKey:@"minDate"]; @@ -395,6 +422,9 @@ CPEraDatePickerElementFlag = 0x0100; */ - (void)setMaxDate:(CPDate)aMaxDate { + if (_maxDate === aMaxDate) + return; + [self willChangeValueForKey:@"maxDate"]; _maxDate = aMaxDate; [self didChangeValueForKey:@"maxDate"]; @@ -407,10 +437,32 @@ CPEraDatePickerElementFlag = 0x0100; */ - (void)setDatePickerStyle:(CPInteger)aDatePickerStyle { + if (_datePickerStyle === aDatePickerStyle) + return; + _datePickerStyle = aDatePickerStyle; + // This is needed in order to specify different theme attributes values for textual / graphical date picker + if (_datePickerStyle === CPClockAndCalendarDatePickerStyle) + [self setThemeState:CPThemeStateAlternateState]; + else + [self unsetThemeState:CPThemeStateAlternateState]; + + // This is needed in order to specify different theme attributes values for with / without stepper textual date picker + if (_datePickerStyle === CPTextFieldAndStepperDatePickerStyle) + [self setThemeState:CPThemeStateComposedControl]; + else + [self unsetThemeState:CPThemeStateComposedControl]; + [self setControlSize:[self controlSize]]; + if (_datePickerComponent) + { + // We already have a component so we need to update it + [_datePickerComponent resignFirstResponder]; + [self _createComponents]; + } + [self setNeedsDisplay:YES]; [self setNeedsLayout]; } @@ -420,8 +472,14 @@ CPEraDatePickerElementFlag = 0x0100; */ - (void)setDatePickerElements:(CPInteger)aDatePickerElements { + if (_datePickerElements === aDatePickerElements) + return; + _datePickerElements = aDatePickerElements; + // Notify the component of the new value + [_datePickerComponent setDatePickerElements:_datePickerElements]; + [self setNeedsDisplay:YES]; [self setNeedsLayout]; } @@ -431,6 +489,9 @@ CPEraDatePickerElementFlag = 0x0100; */ - (void)setDatePickerMode:(CPInteger)aDatePickerMode { + if (_datePickerMode === aDatePickerMode) + return; + _datePickerMode = aDatePickerMode; if (_datePickerMode == CPSingleDateMode) @@ -456,6 +517,9 @@ CPEraDatePickerElementFlag = 0x0100; */ - (void)setLocale:(CPLocale)aLocale { + if (_locale === aLocale) + return; + _locale = aLocale; if (_formatter) @@ -466,7 +530,9 @@ CPEraDatePickerElementFlag = 0x0100; } // This will update the textFields (usefull when changing with a date with pm and am) - [_datePickerTextfield setDateValue:_dateValue]; + if (_isTextual) + [_datePickerComponent setDateValue:_dateValue]; + [self setNeedsDisplay:YES]; [self setNeedsLayout]; } @@ -477,6 +543,9 @@ CPEraDatePickerElementFlag = 0x0100; */ - (void)setBezeled:(BOOL)shouldBeBezeled { + if (_isBezeled === shouldBeBezeled) + return; + _isBezeled = shouldBeBezeled; if (shouldBeBezeled) @@ -491,6 +560,9 @@ CPEraDatePickerElementFlag = 0x0100; */ - (void)setBordered:(BOOL)shouldBeBordered { + if (_isBordered === shouldBeBordered) + return; + _isBordered = shouldBeBordered; if (shouldBeBordered) @@ -506,6 +578,22 @@ CPEraDatePickerElementFlag = 0x0100; - (void)setTextFont:(CPFont)aFont { [self setFont:aFont]; + + if (_isTextual) + [_datePickerComponent setTextFont:aFont]; +} + +/*! + Sets the color of the control. + @param aColor + */ +- (void)setTextColor:(CPColor)aColor +{ + [super setTextColor:aColor]; + + if (_isTextual) + [_datePickerComponent setTextColor:aColor]; + // REM: in Cocoa, setTextColor has no effect on calendar view } /*! Sets the enabled status of the control. Controls that are not enabled can not be used by the user and obtain the CPThemeStateDisabled theme state. @@ -515,8 +603,10 @@ CPEraDatePickerElementFlag = 0x0100; { [super setEnabled:aBoolean]; - [_datePickerTextfield setEnabled:aBoolean]; - [_datePickerCalendar setEnabled:aBoolean]; + [_datePickerComponent setEnabled:aBoolean]; + + if (!aBoolean) + [self resignFirstResponder]; } /*! Set the background color of the datePicker @@ -533,6 +623,9 @@ CPEraDatePickerElementFlag = 0x0100; */ - (void)setDrawsBackground:(BOOL)aBoolean { + if (_drawsBackground === aBoolean) + return; + [self willChangeValueForKey:@"drawsBackground"]; _drawsBackground = aBoolean; [self didChangeValueForKey:@"drawsBackground"]; @@ -545,16 +638,16 @@ CPEraDatePickerElementFlag = 0x0100; */ - (void)setTimeZone:(CPTimeZone)aTimeZone { + if (_timeZone === aTimeZone) + return; + [self willChangeValueForKey:@"timeZone"]; _timeZone = aTimeZone; [self didChangeValueForKey:@"timeZone"]; [self setNeedsLayout]; - if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle) - [_datePickerTextfield setDateValue:_dateValue]; - else - [_datePickerCalendar setDateValue:_dateValue]; + [_datePickerComponent setDateValue:_dateValue]; } @@ -565,12 +658,12 @@ CPEraDatePickerElementFlag = 0x0100; */ - (BOOL)becomeFirstResponder { - if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle) + if (_isTextual) { if (![super becomeFirstResponder]) return NO; - [_datePickerTextfield _selectTextFieldWithFlags:[[CPApp currentEvent] modifierFlags]]; + [_datePickerComponent _selectTextFieldWithFlags:[[CPApp currentEvent] modifierFlags]]; return YES; } @@ -589,8 +682,8 @@ CPEraDatePickerElementFlag = 0x0100; */ - (BOOL)resignFirstResponder { - if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle) - [_datePickerTextfield resignFirstResponder]; + if (_isTextual) + [_datePickerComponent resignFirstResponder]; return YES; } @@ -638,8 +731,8 @@ CPEraDatePickerElementFlag = 0x0100; */ - (void)keyDown:(CPEvent)anEvent { - if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle) - [_datePickerTextfield keyDown:anEvent]; + if (_isTextual) + [_datePickerComponent keyDown:anEvent]; } @end @@ -665,20 +758,22 @@ var CPDatePickerModeKey = @"CPDatePickerModeKey", if (self) { - _textFont = [aCoder decodeObjectForKey:CPTextFontKey]; - _minDate = [aCoder decodeObjectForKey:CPMinDateKey] || [CPDate distantPast]; - _maxDate = [aCoder decodeObjectForKey:CPMaxDateKey] || [CPDate distantFuture]; - _timeInterval = [aCoder decodeDoubleForKey:CPIntervalKey]; - _datePickerMode = [aCoder decodeIntForKey:CPDatePickerModeKey]; - _datePickerElements = [aCoder decodeIntForKey:CPDatePickerElementsKey]; - [self setDatePickerStyle:[aCoder decodeIntForKey:CPDatePickerStyleKey]]; - _locale = [aCoder decodeObjectForKey:CPLocaleKey]; - _dateValue = [aCoder decodeObjectForKey:CPDateValueKey]; _backgroundColor = [aCoder decodeObjectForKey:CPBackgroundColorKey]; - _drawsBackground = [aCoder decodeBoolForKey:CPDrawsBackgroundKey]; - _isBordered = [aCoder decodeBoolForKey:CPBorderedKey]; + [self setBordered:[aCoder decodeBoolForKey:CPBorderedKey]]; + [self setDrawsBackground:[aCoder decodeBoolForKey:CPDrawsBackgroundKey]]; + + [self setDatePickerElements:[aCoder decodeIntForKey:CPDatePickerElementsKey]]; + [self setDatePickerMode:[aCoder decodeIntForKey:CPDatePickerModeKey]]; + [self setDatePickerStyle:[aCoder decodeIntForKey:CPDatePickerStyleKey]]; + [self setMinDate:[aCoder decodeObjectForKey:CPMinDateKey] || [CPDate distantPast]]; + [self setMaxDate:[aCoder decodeObjectForKey:CPMaxDateKey] || [CPDate distantFuture]]; + [self setLocale:[aCoder decodeObjectForKey:CPLocaleKey]]; [self _init]; + + [self setTextFont:[aCoder decodeObjectForKey:CPTextFontKey]]; + [self setTimeInterval:[aCoder decodeDoubleForKey:CPIntervalKey]]; + [self setDateValue:[aCoder decodeObjectForKey:CPDateValueKey]]; } return self @@ -686,7 +781,10 @@ var CPDatePickerModeKey = @"CPDatePickerModeKey", - (void)encodeWithCoder:(CPCoder)aCoder { - // FIXME Do we need to encode _datePickerTextfield and _datePickerCalendar? As subviews they'll be encoded, but when we decode we recreate them anyhow. + // Before encoding, we remove all subviews as we'll recreate them at loading + while ([[self subviews] count] > 0) + [[[self subviews] lastObject] removeFromSuperview]; + [super encodeWithCoder:aCoder]; [aCoder encodeDouble:_timeInterval forKey:CPIntervalKey]; diff --git a/AppKit/CPDatePicker/_CPDatePickerBox.j b/AppKit/CPDatePicker/_CPDatePickerBox.j new file mode 100644 index 000000000..9e19918ce --- /dev/null +++ b/AppKit/CPDatePicker/_CPDatePickerBox.j @@ -0,0 +1,98 @@ +/* _CPDatePickerBox.j +* AppKit +* +* Created by Alexandre Wilhelm +* Copyright 2012 +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +@import +@import "CPView.j" + +@class CPDatePicker + +@implementation _CPDatePickerBox : CPView +{ + CPDatePicker _datePicker @accessors(property=datePicker); +} + +- (void)drawRect:(CGRect)aRect +{ + [super drawRect:aRect]; + + if ([_datePicker isCSSBased]) + return; + + if ([_datePicker isBordered]) + { + var context = [[CPGraphicsContext currentContext] graphicsPort], + borderWidth = [_datePicker valueForThemeAttribute:@"border-width"] / 2; + + CGContextBeginPath(context); + CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:[_datePicker themeState]]); + CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]); + + CGContextMoveToPoint(context, borderWidth, borderWidth); + CGContextAddLineToPoint(context, aRect.size.width - borderWidth, borderWidth); + CGContextAddLineToPoint(context, aRect.size.width - borderWidth, aRect.size.height - borderWidth); + CGContextAddLineToPoint(context, borderWidth, aRect.size.height - borderWidth); + CGContextAddLineToPoint(context, borderWidth,borderWidth); + + CGContextStrokePath(context); + CGContextClosePath(context); + } +} + +- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName +{ + if (aName === "bezel-view") + return [self bounds]; + + return [super rectForEphemeralSubviewNamed:aName]; +} + +- (CPView)createEphemeralSubviewNamed:(CPString)aName +{ + if (aName === "bezel-view") + { + var view = [[CPView alloc] initWithFrame:CGRectMakeZero()]; + + [view setHitTests:NO]; + + return view; + } + + return [super createEphemeralSubviewNamed:aName]; +} + +- (void)layoutSubviews +{ + if ([_datePicker isCSSBased]) + { + var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view" + positioned:CPWindowBelow + relativeToEphemeralSubviewNamed:nil]; + + [bezelView setBackgroundColor:[_datePicker currentValueForThemeAttribute:@"bezel-color"]]; + } + + if ([_datePicker drawsBackground]) + [self setBackgroundColor:[_datePicker backgroundColor]]; + else + [self setBackgroundColor:[CPColor clearColor]]; +} + +@end diff --git a/AppKit/CPDatePicker/_CPDatePickerCalendar.j b/AppKit/CPDatePicker/_CPDatePickerCalendar.j index f7fe81f87..af43569b3 100644 --- a/AppKit/CPDatePicker/_CPDatePickerCalendar.j +++ b/AppKit/CPDatePicker/_CPDatePickerCalendar.j @@ -19,27 +19,16 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -@import "CPControl.j" -@import "CPTextField.j" -@import "CPButton.j" -@import "CPView.j" -@import "CPBezierPath.j" -@import "_CPDatePickerClock.j" - @import +@import "_CPDatePickerClock.j" +@import "_CPDatePickerBox.j" +@import "_CPDatePickerMonthView.j" +@import "_CPDatePickerHeaderView.j" + @class CPDatePicker -@class _CPDatePickerMonthView -@class _CPDatePickerHeaderView -@class _CPDatePickerBox @global CPApp -@global CPSingleDateMode -@global CPRangeDateMode - -@global CPTextFieldAndStepperDatePickerStyle -@global CPTextFieldDatePickerStyle - @global CPHourMinuteDatePickerElementFlag @global CPHourMinuteSecondDatePickerElementFlag @global CPTimeZoneDatePickerElementFlag @@ -47,20 +36,6 @@ @global CPYearMonthDayDatePickerElementFlag @global CPEraDatePickerElementFlag -@global CPAlternateKeyMask -@global CPCommandKeyMask -@global CPControlKeyMask - -var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su"], - CPShortWeekDayNameArrayUS = [@"Su", @"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa"], - CPShortWeekDayNameArrayFr = [@"L", @"M", @"M", @"J", @"V", @"S", @"D"], - CPShortWeekDayNameArrayDe = [@"M", @"D", @"M", @"D", @"F", @"S", @"S"], - CPShortWeekDayNameArrayEs = [@"L", @"M", @"X", @"J", @"V", @"S", @"D"], - CPShortMonthNameArrayEn = [@"Jan", @"Feb", @"Mar", @"Apr", @"May", @"Jun", @"Jul", @"Aug", @"Sep", @"Oct", @"Nov", @"Dec"], - CPShortMonthNameArrayFr = [@"janv.", String.fromCharCode(102, 233, 118, 46), @"mars", @"apr.", @"mai", @"juin", @"juil.", String.fromCharCode(97, 111, 251, 116), @"sept.", @"oct.", @"nov.", String.fromCharCode(100, 233, 99, 46)], - CPShortMonthNameArrayDe = [@"Jan", @"Feb", String.fromCharCode(77, 228, 114), @"Apr", @"Mai", @"Jun", @"Jul", @"Aug", @"Sep", @"Okt", @"Nov", @"Dez"], - CPShortMonthNameArrayEs = [@"ene", @"feb", @"mar", @"abr", @"may", @"jun", @"jul", @"ago", @"sep", @"oct", @"nov", @"dic"]; - @implementation _CPDatePickerCalendar : CPControl { _CPDatePickerMonthView _monthView; @@ -70,10 +45,13 @@ var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su" CPDatePicker _datePicker; CPInteger _startSelectionIndex; CPInteger _currentSelectionIndex; + BOOL _hasClock; + BOOL _hasCalendar; + BOOL _isClockOnly; + CPInteger _datePickerElements @accessors(getter=datePickerElements); } -#pragma mark - #pragma mark Init method /*! Init a _CPDatePickerCalendar @@ -85,7 +63,9 @@ var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su" { if (self = [super initWithFrame:aFrame]) { - _datePicker = aDatePicker; + _datePicker = aDatePicker; + _datePickerElements = [_datePicker datePickerElements]; + [self _init]; } @@ -96,26 +76,38 @@ var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su" */ - (void)_init { - var sizeHeader = [_datePicker valueForThemeAttribute:@"size-header"], - sizeCalendar = [_datePicker valueForThemeAttribute:@"size-calendar"], - sizeClock = [_datePicker valueForThemeAttribute:@"size-clock"]; + var sizeHeader = [_datePicker valueForThemeAttribute:@"size-header"], + sizeCalendar = [_datePicker valueForThemeAttribute:@"size-calendar"], + sizeClock = [_datePicker valueForThemeAttribute:@"size-clock"], + calendarClockMargin = [_datePicker valueForThemeAttribute:@"calendar-clock-margin"]; - _box = [[_CPDatePickerBox alloc] initWithFrame:CGRectMake(0, 0, sizeCalendar.width, sizeHeader.height + sizeCalendar.height)]; - [_box setDatePicker:_datePicker]; + _hasClock = (_datePickerElements & CPHourMinuteSecondDatePickerElementFlag) || (_datePickerElements & CPHourMinuteDatePickerElementFlag); + _hasCalendar = (_datePickerElements & CPYearMonthDayDatePickerElementFlag) || (_datePickerElements & CPYearMonthDatePickerElementFlag); + _isClockOnly = _hasClock && !_hasCalendar; - _headerView = [[_CPDatePickerHeaderView alloc] initWithFrame:CGRectMake(0, 0, sizeHeader.width, sizeHeader.height) datePicker:_datePicker delegate:self]; - [_box addSubview:_headerView]; + if (_hasCalendar && !_box) + { + _box = [[_CPDatePickerBox alloc] initWithFrame:CGRectMake(0, 0, sizeCalendar.width, sizeHeader.height + sizeCalendar.height)]; + [_box setDatePicker:_datePicker]; + [self addSubview:_box]; - _monthView = [[_CPDatePickerMonthView alloc] initWithFrame:CGRectMake(0, sizeHeader.height, sizeCalendar.width, sizeCalendar.height) datePicker:_datePicker delegate:self]; - [_box addSubview:_monthView]; + _headerView = [[_CPDatePickerHeaderView alloc] initWithFrame:CGRectMake(0, 0, sizeHeader.width, sizeHeader.height) datePicker:_datePicker delegate:self]; + [_box addSubview:_headerView]; - _datePickerClock = [[_CPDatePickerClock alloc] initWithFrame:CGRectMake(sizeCalendar.width + 10, sizeHeader.height + sizeCalendar.height / 2 - sizeClock.height / 2, sizeClock.width, sizeClock.height) datePicker:_datePicker]; - [_datePickerClock setHidden:YES]; - [self addSubview:_datePickerClock]; + _monthView = [[_CPDatePickerMonthView alloc] initWithFrame:CGRectMake(0, sizeHeader.height, sizeCalendar.width, sizeCalendar.height) datePicker:_datePicker delegate:self]; + [_box addSubview:_monthView]; + } - [self addSubview:_box]; + if (_hasClock && !_datePickerClock) + { + _datePickerClock = [[_CPDatePickerClock alloc] initWithFrame:CGRectMake(0, 0, sizeClock.width, sizeClock.height) datePicker:_datePicker]; + [self addSubview:_datePickerClock]; + } - [self setNeedsLayout]; + if (_hasClock) + [_datePickerClock setDatePickerElements:_datePickerElements]; + + [self setDateValue:[_datePicker dateValue]]; } @@ -158,6 +150,15 @@ var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su" [_monthView setEnabled:aBoolean]; } +- (void)setDatePickerElements:(CPInteger)aDatePickerElements +{ + if (_datePickerElements === aDatePickerElements) + return; + + _datePickerElements = aDatePickerElements; + + [self _init]; +} #pragma mark - #pragma mark Layout methods @@ -166,28 +167,21 @@ var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su" */ - (void)layoutSubviews { - if ([_datePicker datePickerStyle] == CPTextFieldAndStepperDatePickerStyle || [_datePicker datePickerStyle] == CPTextFieldDatePickerStyle) - return; - var minSize = [_datePicker valueForThemeAttribute:@"min-size-calendar"], sizeHeader = [_datePicker valueForThemeAttribute:@"size-header"], sizeCalendar = [_datePicker valueForThemeAttribute:@"size-calendar"], - sizeClock = [_datePicker valueForThemeAttribute:@"size-clock"]; + sizeClock = [_datePicker valueForThemeAttribute:@"size-clock"], + calendarClockMargin = [_datePicker valueForThemeAttribute:@"calendar-clock-margin"]; - [super layoutSubviews]; - - if ([_datePicker datePickerElements] & CPHourMinuteSecondDatePickerElementFlag || [_datePicker datePickerElements] & CPHourMinuteDatePickerElementFlag) + if (_hasClock) { - [_datePickerClock setHidden:NO]; - [_datePickerClock setNeedsLayout]; - - if ([_datePicker datePickerElements] & CPYearMonthDatePickerElementFlag || [_datePicker datePickerElements] & CPYearMonthDayDatePickerElementFlag) + if (!_isClockOnly) { var frameSize = CGSizeMakeCopy(minSize); - frameSize.width += sizeClock.width + 10; + frameSize.width += sizeClock.width + calendarClockMargin; [_datePicker setFrameSize:frameSize]; - [_datePickerClock setFrameOrigin:CGPointMake(sizeCalendar.width + 10, [self bounds].size.height / 2 - sizeClock.height / 2)]; + [_datePickerClock setFrameOrigin:CGPointMake(sizeCalendar.width + calendarClockMargin, [self bounds].size.height / 2 - sizeClock.height / 2)]; } else { @@ -195,6 +189,9 @@ var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su" [_datePickerClock setFrameOrigin:CGPointMake(0, 0)]; } + [_datePickerClock setHidden:NO]; + [_datePickerClock setFrameSize:sizeClock]; + [_datePickerClock setNeedsLayout]; } else { @@ -202,7 +199,7 @@ var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su" [_datePickerClock setHidden:YES]; } - if ([_datePicker datePickerElements] & CPYearMonthDatePickerElementFlag || [_datePicker datePickerElements] & CPYearMonthDayDatePickerElementFlag) + if (_hasCalendar) { [_box setHidden:NO]; [_headerView setHidden:NO]; @@ -295,1018 +292,3 @@ var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su" } @end - - -@implementation _CPDatePickerHeaderView : CPControl -{ - CPArray _dayLabels; - CPArray _monthNames; - CPButton _nextButton; - CPButton _previousButton; - CPButton _currentButton; - CPDatePicker _datePicker; - CPDate _date; - CPTextField _title; -} - - -#pragma mark - -#pragma mark Init methods - -/*! Init a new instance of _CPDatePickerHeaderView - @param aFrame - @param aDatePicker - @return a new instance of _CPDatePickerHeaderView -*/ -- (id)initWithFrame:(CGRect)aFrame datePicker:(CPDatePicker)aDatePicker delegate:(id)aDelegate -{ - self = [super initWithFrame:aFrame]; - - if (self) - { - _datePicker = aDatePicker; - - // Title - _title = [[CPTextField alloc] initWithFrame:CGRectMakeZero()]; - - [_title setValue:[_datePicker valueForThemeAttribute:@"title-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal]; - [_title setValue:[_datePicker valueForThemeAttribute:@"title-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; - [_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; - [_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; - - [_title setValue:[_datePicker valueForThemeAttribute:@"title-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled]; - [_title setValue:[_datePicker valueForThemeAttribute:@"title-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; - [_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; - [_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; - - [self addSubview:_title]; - - _dayLabels = [CPArray array]; - - // Days - for (var i = 0; i < [[self _dayNames] count]; i++) - { - var label = [[CPTextField alloc] initWithFrame:CGRectMakeZero()]; - [label setValue:CPCenterTextAlignment forThemeAttribute:@"alignment"]; - - [label setValue:[_datePicker valueForThemeAttribute:@"weekday-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal]; - [label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; - [label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; - [label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; - - [label setValue:[_datePicker valueForThemeAttribute:@"weekday-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled]; - [label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; - [label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; - [label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; - - [_dayLabels addObject:label]; - [self addSubview:label]; - } - - // Arrows - _previousButton = [[CPButton alloc] initWithFrame:CGRectMakeZero()]; - [_previousButton setButtonType:CPMomentaryChangeButton]; - [_previousButton setBordered:NO]; - [_previousButton setImage:[_datePicker valueForThemeAttribute:@"arrow-image-left"]]; - [_previousButton setAlternateImage:[_datePicker valueForThemeAttribute:@"arrow-image-left-highlighted"]]; - [self addSubview:_previousButton]; - - _nextButton = [[CPButton alloc] initWithFrame:CGRectMakeZero()]; - [_nextButton setButtonType:CPMomentaryChangeButton]; - [_nextButton setBordered:NO]; - [_nextButton setImage:[_datePicker valueForThemeAttribute:@"arrow-image-right"]]; - [_nextButton setAlternateImage:[_datePicker valueForThemeAttribute:@"arrow-image-right-highlighted"]]; - [self addSubview:_nextButton]; - - _currentButton = [[CPButton alloc] initWithFrame:CGRectMakeZero()]; - [_currentButton setButtonType:CPMomentaryChangeButton]; - [_currentButton setBordered:NO]; - [_currentButton setImage:[_datePicker valueForThemeAttribute:@"circle-image"]]; - [_currentButton setAlternateImage:[_datePicker valueForThemeAttribute:@"circle-image-highlighted"]]; - [self addSubview:_currentButton]; - - [_previousButton setTarget:aDelegate]; - [_previousButton setAction:@selector(_clickArrowPrevious:)]; - [_previousButton setContinuous:YES]; - - [_nextButton setTarget:aDelegate]; - [_nextButton setAction:@selector(_clickArrowNext:)]; - [_nextButton setContinuous:YES]; - - [_currentButton setTarget:aDelegate]; - [_currentButton setAction:@selector(_currentMonth:)]; - - [self setNeedsLayout]; - } - - return self; -} - - -#pragma mark - -#pragma mark Getter Setter methods - -/*! Return the day names depending on the CPLocale of the datePicker - @return an array -*/ -- (CPArray)_dayNames -{ - switch ([[_datePicker locale] objectForKey:CPLocaleLanguageCode]) - { - case @"en": - - // Check if it's in the american format. If yes the week will begin the sunday - if ([_datePicker _isAmericanFormat]) - return CPShortWeekDayNameArrayUS; - else - return CPShortWeekDayNameArrayEn; - break; - - case @"es": - return CPShortWeekDayNameArrayEs; - break; - - case @"de": - return CPShortWeekDayNameArrayDe; - break; - - case @"fr": - return CPShortWeekDayNameArrayFr; - break; - - default: - return CPShortWeekDayNameArrayEn; - break; - } -} - -/*! Return the month names depending on the CPLocale of the datePicker - @return an array -*/ -- (CPArray)_monthNames -{ - switch ([[_datePicker locale] objectForKey:CPLocaleLanguageCode]) - { - case @"en": - return CPShortMonthNameArrayEn; - break; - - case @"es": - return CPShortMonthNameArrayEs; - break; - - case @"de": - return CPShortMonthNameArrayDe; - break; - - case @"fr": - return CPShortMonthNameArrayFr; - break; - - default: - return CPShortMonthNameArrayEn; - break; - } - -} - -/*! Set the monthDate of the header - @aMonthDate the new monthDate -*/ -- (void)setMonthForDate:(CPDate)aMonthDate -{ - _date = aMonthDate; - [self setNeedsLayout]; -} - -/*! Set enabled - @param aBoolean -*/ -- (void)setEnabled:(BOOL)aBoolean -{ - [_previousButton setEnabled:aBoolean]; - [_nextButton setEnabled:aBoolean]; - [_currentButton setEnabled:aBoolean]; - [_dayLabels makeObjectsPerformSelector:@selector(setEnabled:) withObject:aBoolean]; - [_title setEnabled:aBoolean]; -} - - -#pragma mark - -#pragma mark Layout methods - -/*! Layout the subviews -*/ -- (void)layoutSubviews -{ - if ([_datePicker datePickerStyle] == CPTextFieldAndStepperDatePickerStyle || [_datePicker datePickerStyle] == CPTextFieldDatePickerStyle) - return; - - var bounds = [self bounds], - dayNames = [self _dayNames], - width = CGRectGetWidth(bounds), - buttonInset = [_datePicker valueForThemeAttribute:@"arrow-inset"], - numberOfLabels = [_dayLabels count], - labelWidth = width / numberOfLabels, - sizeButtonLeft = [[_datePicker valueForThemeAttribute:@"arrow-image-left"] size], - sizeButtonRight = [[_datePicker valueForThemeAttribute:@"arrow-image-right"] size], - sizeButtonCircle = [[_datePicker valueForThemeAttribute:@"circle-image"] size], - sizeTileWidth = [_datePicker valueForThemeAttribute:@"size-tile"].width; - - // Arrows - [_nextButton setFrame:CGRectMake(width - [_nextButton frameSize].width - buttonInset.right, buttonInset.top, sizeButtonRight.width, sizeButtonRight.height)]; - [_currentButton setFrame:CGRectMake(CGRectGetMinX([_nextButton frame]) - sizeButtonCircle.width - buttonInset.left - buttonInset.right, buttonInset.top, sizeButtonCircle.width, sizeButtonCircle.height)]; - [_previousButton setFrame:CGRectMake(CGRectGetMinX([_currentButton frame]) - sizeButtonLeft.width - buttonInset.left - buttonInset.right, buttonInset.top, sizeButtonLeft.width, sizeButtonLeft.height)]; - - var firstDayTileX; - - // Weekday label - for (var i = 0; i < numberOfLabels; i++) - { - var dayLabel = _dayLabels[i]; - - [dayLabel setStringValue:dayNames[i]]; - [dayLabel sizeToFit]; - [dayLabel setFrameOrigin:CGPointMake(sizeTileWidth * (i + 1) - sizeTileWidth / 2 - [dayLabel frameSize].width / 2, 23)]; - - if (i == 0) - firstDayTileX = sizeTileWidth * (i + 1) - sizeTileWidth / 2 - [dayLabel frameSize].width / 2; - } - - // Title - [_title setStringValue:[CPString stringWithFormat:@"%s %i", [self _monthNames][_date.getMonth()], _date.getFullYear()]]; - [_title sizeToFit]; - [_title setFrameOrigin:CGPointMake(firstDayTileX, 6)]; -} - -@end - - -@implementation _CPDatePickerMonthView : CPControl -{ - BOOL _isMonthJustChanged; - CPArray _dayTiles; - CPDate _clickDate; - CPDate _dragDate; - CPDate _date; - CPDate _previousMonth @accessors(property=previousMonth); - CPDate _nextMonth @accessors(property=nextMonth); - CPDatePicker _datePicker; - CPEvent _eventDragged; - CPTimer _timerMonth; - id _delegate; - int _indexDayTile; -} - - -#pragma mark - -#pragma mark Init methods - -/*! Init a _CPDatePickerMonthView - @param aFrame - @param aDatePicker - @return a new _CPDatePickerMonthView -*/ -- (id)initWithFrame:(CGRect)aFrame datePicker:(CPDatePicker)aDatePicker delegate:(id)aDelegate -{ - if (self = [super initWithFrame:aFrame]) - { - _delegate = aDelegate; - _isMonthJustChanged = NO; - _indexDayTile = -1; - _datePicker = aDatePicker; - _dayTiles = [CPArray array]; - - // Create tiles - for (var i = 0; i < 42; i++) - { - var dayView = [[_CPDatePickerDayView alloc] initWithFrame:CGRectMakeZero() withDatePicker:_datePicker]; - [self addSubview:dayView]; - [_dayTiles addObject:dayView]; - } - - [self setNeedsLayout]; - } - return self; -} - - -#pragma mark - -#pragma mark Getter Setter methods - -/*! Set the monthDate of the component - @param aDate -*/ -- (void)setMonthForDate:(CPDate)aDate -{ - if (_dragDate) - { - if (_dragDate.getMonth() != _date.getMonth()) - _isMonthJustChanged = YES; - - _date = [_dragDate copy]; - } - else - { - _date = [aDate copy]; - } - - if (![aDate isEqualToDate:[CPDate distantFuture]]) - { - // Reset the date to the first day of the month & midnight - _date.setDate(1); - [_date _resetToMidnight]; - - // There must be a better way to do this. - var firstDay = [_date copy]; - firstDay.setDate(1); - - // Set the previous and next month date. This is usefull for the tile of the next/previous month - _previousMonth = new Date(firstDay.getTime() - 86400000); - _previousMonth.setDate(1); - - _nextMonth = new Date(firstDay.getTime() + (([_date _daysInMonth] + 1) * 86400000)); - _nextMonth.setDate(1); - } - - [self reloadData]; - - if (_isMonthJustChanged) - { - var dayTile = [_dayTiles objectAtIndex:_indexDayTile]; - - if ([dayTile date].getMonth() == _date.getMonth()) - { - [self mouseDragged:_eventDragged]; - } - else - { - if ([dayTile date].getMonth() - _date.getMonth() == 1 || [dayTile date].getFullYear() - _date.getFullYear() == 1) - _timerMonth = [CPTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(_timerNextMonthEvent:) userInfo:nil repeats:NO]; - else - _timerMonth = [CPTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(_timerPreviousMonthEvent:) userInfo:nil repeats:NO]; - } - } -} - -- (void)monthDate -{ - return _date; -} - -/*! Return the size of a tile -*/ -- (CGSize)tileSize -{ - return [_datePicker valueForThemeAttribute:@"size-tile"]; -} - -/*! Return the first index day of the month -*/ -- (int)startOfWeekForDate:(CPDate)aDate -{ - var day = aDate.getDay(); - - // American people begins the week the sunday - if (![_datePicker _isAmericanFormat]) - return (day + 6) % 7; - - return day; -} - -/*! Set enabled - @param aBoolean -*/ -- (void)setEnabled:(BOOL)aBoolean -{ - [super setEnabled:aBoolean]; - [self reloadData]; -} - - -/*! Return the index of tile depending of the giving event - @param anEvent - @return an index -*/ -- (CPInteger)indexOfTileForEvent:(CPEvent)anEvent -{ - var locationInView = [self convertPoint:[anEvent locationInWindow] fromView:nil], - tileSize = [self tileSize]; - - // Get the week row - var rowIndex = FLOOR(locationInView.y / tileSize.height), - columnIndex = FLOOR(locationInView.x / tileSize.width); - - columnIndex = MIN(MAX(columnIndex, 0), 6); - rowIndex = MIN(MAX(rowIndex, 0), 5); - - var tileIndex = (rowIndex * 7) + columnIndex; - - return tileIndex; -} - -#pragma mark - -#pragma mark Reload data - -/*! Reload the data -*/ -- (void)reloadData -{ - if (!_date) - return; - - var currentMonth = _date, - startOfMonthDay = [self startOfWeekForDate:currentMonth], - daysInPreviousMonth = [_previousMonth _daysInMonth], - firstDayToShowInPreviousMonth = daysInPreviousMonth - startOfMonthDay, - currentDate = new Date(_previousMonth.getFullYear(), _previousMonth.getMonth(), firstDayToShowInPreviousMonth), - now = [CPDate date], - dateValue = [_datePicker dateValue]; - - // Update the tiles - for (var i = 0; i < [_dayTiles count]; i++) - { - var dayTile = _dayTiles[i]; - - // Increment to next day - currentDate.setTime(currentDate.getTime() + 90000000); - [currentDate _resetToMidnight]; - - var isPresentMonth = (now.getMonth() == currentDate.getMonth() - && now.getFullYear() == currentDate.getFullYear()); - - [dayTile setDate:[currentDate copy]]; - [dayTile setStringValue:currentDate.getDate()]; - [dayTile setDisabled:![self isEnabled] || currentDate.getMonth() !== currentMonth.getMonth() || currentDate < [_datePicker minDate] || currentDate > [_datePicker maxDate]]; - [dayTile setHighlighted:isPresentMonth && currentDate.getDate() == now.getDate()]; - } - - // Select the dates - [self _selectDate:[_datePicker dateValue] timeInterval:[_datePicker timeInterval]]; -} - - -#pragma mark - -#pragma mark Select methods - -/*! Select one date or several date depending of the giving interval - @param aStartDate - @param anInterval; -*/ -- (void)_selectDate:(CPDate)aStartDate timeInterval:(CPInteger)anInterval -{ - var endDate = [[CPDate alloc] initWithTimeInterval:anInterval sinceDate:aStartDate], - tilesCount = [_dayTiles count]; - - aStartDate = [aStartDate copy]; - - [aStartDate _resetToMidnight]; - [endDate _resetToMidnight]; - - for (var i = 0; i < tilesCount; i++) - { - var tile = _dayTiles[i], - tileDate = [[tile date] copy], - selected = NO; - - [tileDate _resetToMidnight]; - - if (aStartDate) - selected = tileDate >= aStartDate && tileDate <= endDate; - - // Select a tile - [tile setSelected:selected]; - } -} - -#pragma mark - -#pragma mark Layout methods - -/*! Tile the view -*/ -- (void)tile -{ - var tileSize = [self tileSize], - width = tileSize.width, - height = tileSize.height, - tilesCount = [_dayTiles count], - borderWidth = [_datePicker valueForThemeAttribute:@"border-width"], - tileIndex; - - // Set the frame of the tiles - for (tileIndex = 0; tileIndex < tilesCount; tileIndex++) - { - var dayInWeek = tileIndex % 7, - weekInMonth = (tileIndex - dayInWeek) / 7, - tileFrame = CGRectMake(dayInWeek * width, weekInMonth * height, width + borderWidth, height + borderWidth); - - [_dayTiles[tileIndex] setFrame:tileFrame]; - } - - [self reloadData]; -} - -/*! Layout the subviews -*/ -- (void)layoutSubviews -{ - if ([_datePicker datePickerStyle] == CPTextFieldAndStepperDatePickerStyle || [_datePicker datePickerStyle] == CPTextFieldDatePickerStyle) - return; - - [super layoutSubviews]; - - [self tile]; - [_dayTiles makeObjectsPerformSelector:@selector(setNeedsLayout)]; -} - -/*! Draw the component. This draws the border of the tile. - The selected tile are drawed in the drawRect method of the tile. But the unselected tile here. - It avoids some problems with tiles over other tiles (otherwise the color of the tile border would be different). - Rememeber that the first pixel of a tile are over the last pixel of the last tile (because the border) -*/ -- (void)drawRect:(CGRect)aRect -{ - [super drawRect:aRect]; - - var context = [[CPGraphicsContext currentContext] graphicsPort], - width = [self tileSize].width, - height = [self tileSize].height, - isBorderPair = ([_datePicker valueForThemeAttribute:@"border-width"] % 2) == 0; - - CGContextBeginPath(context); - CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:[_datePicker themeState]]); - CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]); - - if ([_datePicker isBordered]) - { - for (var i = 0; i < 6; i++) - { - var y = i * height; - - // Very usefull to avoid to have a line of two pixels instead one - if (!isBorderPair) - y += 0.5; - - CGContextMoveToPoint(context, 0, y); - CGContextAddLineToPoint(context, [self bounds].size.width, y); - } - - for (var i = 0; i < 7; i++) - { - var x = i * width; - - // Very usefull to avoid to have a line of two pixels instead one - if (!isBorderPair) - x += 0.5; - - CGContextMoveToPoint(context, x, 0); - CGContextAddLineToPoint(context, x, [self bounds].size.height); - } - } - else - { - var y = 0; - - // Very usefull to avoid to have a line of two pixels instead one - if (!isBorderPair) - y += 0.5; - - CGContextMoveToPoint(context, 0, y); - CGContextAddLineToPoint(context, [self bounds].size.width, y); - } - - CGContextStrokePath(context); - CGContextClosePath(context); -} - - -#pragma mark - -#pragma mark Mouse event - -/*! Mouse down event -*/ -- (void)mouseDown:(CPEvent)anEvent -{ - if (![self isEnabled]) - return; - - var dayTile = [_dayTiles objectAtIndex:[self indexOfTileForEvent:anEvent]], - dateTile = [[dayTile date] copy], - dateValue = [_datePicker dateValue]; - - _clickDate = [dateTile copy]; - _dragDate = nil; - _indexDayTile = -1; - _eventDragged = nil; - - _datePicker._invokedByUserEvent = YES; - - // Check if we have to change or not the month of the component - if ([dayTile date].getMonth() == _date.getMonth()) - { - if ([_datePicker datePickerMode] == CPRangeDateMode && [anEvent modifierFlags] & CPShiftKeyMask) - { - var dateValueAtMidnight = [[_datePicker dateValue] copy]; - - [dateValueAtMidnight _resetToMidnight]; - - if (dateTile < dateValueAtMidnight) - { - var interval; - - if (dateTile == dateValueAtMidnight) - interval = [_datePicker timeInterval]; - else - interval = ([dateValueAtMidnight timeIntervalSinceDate:dateTile] + [_datePicker timeInterval]); - - [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:interval]; - } - else if ([[dayTile date] isEqualToDate:dateValueAtMidnight]) - { - [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0]; - } - else - { - [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:[dateValueAtMidnight copy]] timeInterval:([dateTile timeIntervalSinceDate:dateValueAtMidnight])]; - } - - // Be sure to display the good month - [_delegate setDateValue:dateTile]; - } - else - { - var minDate = [[_datePicker minDate] copy], - maxDate = [[_datePicker maxDate] copy]; - - [minDate _resetToMidnight]; - [maxDate _resetToLastSeconds]; - - if (dateTile >= minDate && dateTile <= maxDate) - [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0]; - } - } - else - { - // Check the year and the month. The year is usefull when changing from Jan to Dec. - if (_date.getMonth() - [dayTile date].getMonth() == 1 || _date.getFullYear() - [dayTile date].getFullYear() == 1) - [_delegate _displayPreviousMonth]; - else - [_delegate _displayNextMonth]; - } - - _datePicker._invokedByUserEvent = NO; -} - -/*! Mouse dragged event -*/ -- (void)mouseDragged:(CPEvent)anEvent -{ - if (![self isEnabled] || !CGRectContainsPoint([self bounds],[self convertPoint:[anEvent locationInWindow] fromView:nil])) - return; - - var dayTile = [_dayTiles objectAtIndex:[self indexOfTileForEvent:anEvent]], - dateTile = [[dayTile date] copy], - dateValue = [_datePicker dateValue]; - - _dragDate = [dateTile copy]; - _indexDayTile = [self indexOfTileForEvent:anEvent]; - _eventDragged = anEvent; - - _datePicker._invokedByUserEvent = YES; - - if ([_datePicker datePickerMode] == CPSingleDateMode) - { - // Check if we have to change or not the month of the component - if ([dayTile date].getMonth() == _date.getMonth()) - { - [_timerMonth invalidate]; - _isMonthJustChanged = NO; - - [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0]; - } - else if (!_isMonthJustChanged) - { - [_timerMonth invalidate]; - _isMonthJustChanged = NO; - - // Check the year and the month. The year is usefull when changing from Jan to Dec. - if (_date.getMonth() - [dayTile date].getMonth() == 1 || _date.getFullYear() - [dayTile date].getFullYear() == 1) - [_delegate _displayPreviousMonth]; - else - [_delegate _displayNextMonth]; - } - } - else - { - if (dateTile.getMonth() == _date.getMonth() || !_isMonthJustChanged) - { - [_timerMonth invalidate]; - _isMonthJustChanged = NO; - - var dateValueAtMidnight = [[_datePicker dateValue] copy]; - - [dateValueAtMidnight _resetToMidnight]; - - if (dateTile < _clickDate) - [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:[_clickDate timeIntervalSinceDate:dateTile]]; - else if ([[dayTile date] isEqualToDate:_clickDate]) - [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0]; - else - [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:_clickDate] timeInterval:[dateTile timeIntervalSinceDate:dateValueAtMidnight]]; - } - } - - _datePicker._invokedByUserEvent = NO; -} - -- (void)mouseUp:(CPEvent)anEvent -{ - [_timerMonth invalidate]; - _dragDate = nil; - _clickDate = nil; - _isMonthJustChanged = NO; - _indexDayTile = -1; - _eventDragged = nil; -} - - -#pragma mark - -#pragma mark Timer - -- (void)_timerNextMonthEvent:(CPEvent)anEvent -{ - if (_isMonthJustChanged) - { - _dragDate.setMonth(_date.getMonth() + 1); - [_delegate _displayNextMonth]; - } -} - -- (void)_timerPreviousMonthEvent:(CPEvent)anEvent -{ - if (_isMonthJustChanged) - { - _dragDate.setMonth(_date.getMonth() - 1); - [_delegate _displayPreviousMonth]; - } -} - - -#pragma mark - -#pragma mark Date methods - -- (CPDate)_hoursMinutesSecondsFromDatePickerForDate:(CPDate)aDate -{ - var dateValue = [_datePicker dateValue]; - - aDate.setHours(dateValue.getHours()); - aDate.setMinutes(dateValue.getMinutes()); - aDate.setSeconds(dateValue.getSeconds()); - - return aDate; -} - -@end - - -@implementation _CPDatePickerDayView : CPControl -{ - CPDate _date @accessors(property=date); - - BOOL _isDisabled; - BOOL _isHighlighted; - BOOL _isSelected; - CPDatePicker _datePicker; - CPTextField _textField; -} - - -#pragma mark - -#pragma mark Init methods - -/*! Create a new instance of _CPDatePickerDayView - @param aFrame - @param aDatePicker - @return a new instance of _CPDatePickerDayView -*/ -- (id)initWithFrame:(CGRect)aFrame withDatePicker:(CPDatePicker)aDatePicker -{ - if (self = [super initWithFrame:aFrame]) - { - [self setHitTests:NO]; - - _datePicker = aDatePicker; - - _textField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()]; - - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; - - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateSelected] forThemeAttribute:@"font" inState:CPThemeStateSelected]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateSelected] forThemeAttribute:@"text-color" inState:CPThemeStateSelected]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateSelected] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateSelected]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateSelected] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateSelected]; - - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; - - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateSelected]]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]]forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateSelected]]; - - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateHighlighted] forThemeAttribute:@"font" inState:CPThemeStateHighlighted]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-color" inState:CPThemeStateHighlighted]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateHighlighted]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateHighlighted]; - - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]]; - - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]]; - - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]]; - [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]]; - - [self addSubview:_textField]; - - [self setNeedsLayout]; - } - - return self; -} - - -#pragma mark - -#pragma mark Theme methods - -/*! Set a theme -*/ -- (BOOL)setThemeState:(ThemeState)aState -{ - [_textField setThemeState:aState]; - [super setThemeState:aState]; -} - -/*! Unset a theme -*/ -- (BOOL)unsetThemeState:(ThemeState)aState -{ - [_textField unsetThemeState:aState]; - [super unsetThemeState:aState]; -} - - -#pragma mark - -#pragma mark Getter methods - -/*! Select the tile -*/ -- (void)setSelected:(BOOL)shouldBeSelected -{ - if (_isSelected === shouldBeSelected) - return; - - _isSelected = shouldBeSelected; - - if (_isSelected) - [self setThemeState:CPThemeStateSelected]; - else - [self unsetThemeState:CPThemeStateSelected]; -} - -/*! Disabled the tile (used for previous and next month tile) -*/ -- (void)setDisabled:(BOOL)shouldBeDisabled -{ - if (_isDisabled === shouldBeDisabled) - return; - - _isDisabled = shouldBeDisabled; - - if (_isDisabled) - [self setThemeState:CPThemeStateDisabled]; - else - [self unsetThemeState:CPThemeStateDisabled]; -} - -/*! Highlight the tile (used for current day) -*/ -- (void)setHighlighted:(BOOL)shouldBeHighlighted -{ - if (_isHighlighted === shouldBeHighlighted) - return; - - _isHighlighted = shouldBeHighlighted; - - if (_isHighlighted) - [self setThemeState:CPThemeStateHighlighted]; - else - [self unsetThemeState:CPThemeStateHighlighted]; -} - -/*! Set the stringValue of the tile - @param aStringValue -*/ -- (void)setStringValue:(CPString)aStringValue -{ - [_textField setStringValue:aStringValue]; - [_textField sizeToFit]; -} - - -#pragma mark - -#pragma mark Layout methods - -/*! Layout the subviews -*/ -- (void)layoutSubviews -{ - if ([_datePicker datePickerStyle] == CPTextFieldAndStepperDatePickerStyle || [_datePicker datePickerStyle] == CPTextFieldDatePickerStyle) - return; - - var bounds = [self bounds]; - [_textField sizeToFit]; - [_textField setFrameOrigin:CGPointMake(bounds.size.width / 2 - [_textField frameSize].width / 2 + [_datePicker valueForThemeAttribute:@"border-width"], bounds.size.height / 2 - [_textField frameSize].height / 2)]; -} - -/*! Drawrect -*/ -- (void)drawRect:(CGRect)aRect -{ - [super drawRect:aRect]; - - var themeState = [self themeState], - context = [[CPGraphicsContext currentContext] graphicsPort]; - - if (themeState.hasThemeState(CPThemeStateSelected)) - { - [self setBackgroundColor:[_datePicker valueForThemeAttribute:@"bezel-color-calendar" inState:themeState]]; - CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]); - CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:themeState]); - CGContextAddRect(context, [self bounds]); - CGContextStrokeRect(context, [self bounds]); - } - else - { - // Clear color, because the original color of a tile is handle by his superview - [self setBackgroundColor:[CPColor clearColor]]; - } - -} - -@end - - -@implementation _CPDatePickerBox : CPView -{ - CPDatePicker _datePicker @accessors(property=datePicker); -} - -- (void)drawRect:(CGRect)aRect -{ - [super drawRect:aRect]; - - if ([_datePicker isBordered]) - { - var context = [[CPGraphicsContext currentContext] graphicsPort], - borderWidth = [_datePicker valueForThemeAttribute:@"border-width"] / 2; - - CGContextBeginPath(context); - CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:[_datePicker themeState]]); - CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]); - - CGContextMoveToPoint(context, borderWidth, borderWidth); - CGContextAddLineToPoint(context, aRect.size.width - borderWidth, borderWidth); - CGContextAddLineToPoint(context, aRect.size.width - borderWidth, aRect.size.height - borderWidth); - CGContextAddLineToPoint(context, borderWidth, aRect.size.height - borderWidth); - CGContextAddLineToPoint(context, borderWidth,borderWidth); - - CGContextStrokePath(context); - CGContextClosePath(context); - } -} - -- (void)layoutSubviews -{ - if ([_datePicker datePickerStyle] == CPTextFieldAndStepperDatePickerStyle || [_datePicker datePickerStyle] == CPTextFieldDatePickerStyle) - return; - - if ([_datePicker drawsBackground]) - [self setBackgroundColor:[_datePicker backgroundColor]]; - else - [self setBackgroundColor:[CPColor clearColor]]; -} - -@end diff --git a/AppKit/CPDatePicker/_CPDatePickerClock.j b/AppKit/CPDatePicker/_CPDatePickerClock.j index c18c5e66b..30e9024d3 100644 --- a/AppKit/CPDatePicker/_CPDatePickerClock.j +++ b/AppKit/CPDatePicker/_CPDatePickerClock.j @@ -20,13 +20,18 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +@import +@import @import "CPView.j" @import "CPTextField.j" -@import "CPImageView.j" +@import "CPImage.j" @import "CALayer.j" @class _CPCibCustomResource @class CPDatePicker +@class HandImageLayer +@class HoursLayer +@class HandLayer @global CPHourMinuteSecondDatePickerElementFlag @global CPTextFieldAndStepperDatePickerStyle @@ -34,17 +39,35 @@ var RADIANS = Math.PI / 180; +@typedef _CPDatePickerClockHand +_CPDatePickerClockHours = 1; +_CPDatePickerClockMinutes = 2; +_CPDatePickerClockSeconds = 3; -@implementation _CPDatePickerClock : CPView +@implementation _CPDatePickerClock : CPControl { - BOOL _isEnabled; - CALayer _rootLayer; - CALayer _hourHandLayer; - CALayer _minuteHandLayer; - CALayer _secondHandLayer; - CALayer _middleHandLayer; - CPDatePicker _datePicker; - CPTextField _PMAMTextField; + BOOL _isEnabled; + HoursLayer _rootLayer; + HandLayer _hourHandLayer; + HandLayer _minuteHandLayer; + HandLayer _secondHandLayer; + CALayer _middleHandLayer; + CPDatePicker _datePicker; + CPTextField _PMAMTextField; + + CALayer _currentHandLayer; + _CPDatePickerClockHand _currentHand; + CPInteger _currentRepresentedValue; + float _currentValueShift; + CPInteger _numberOfUnits; + BOOL _trackingHand; + + CPInteger _representedHours; + CPInteger _representedMinutes; + CPInteger _representedSeconds; + BOOL _representedHourIsPM; + + CPInteger _datePickerElements @accessors(getter=datePickerElements); } @@ -55,7 +78,9 @@ var RADIANS = Math.PI / 180; { if (self = [super initWithFrame:aFrame]) { - _datePicker = aDatePicker; + _datePicker = aDatePicker; + _datePickerElements = [_datePicker datePickerElements]; + _trackingHand = NO; _PMAMTextField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()]; @@ -73,7 +98,7 @@ var RADIANS = Math.PI / 180; var middleHandSize = [_datePicker valueForThemeAttribute:@"middle-hand-size"], minuteHandSize = [_datePicker valueForThemeAttribute:@"minute-hand-size"], - hourHandSize = [_datePicker valueForThemeAttribute:@"hour-hand-size"], + hourHandSize = [_datePicker valueForThemeAttribute:@"hour-hand-size"], secondHandSize = [_datePicker valueForThemeAttribute:@"second-hand-size"]; // We use layer to make the rotation possible @@ -97,106 +122,132 @@ var RADIANS = Math.PI / 180; [_middleHandLayer setAnchorPoint:CGPointMakeZero()]; [_middleHandLayer setPosition:CGPointMake(0.0, 0.0)]; - _rootLayer = [CALayer layer]; + _rootLayer = [[HoursLayer alloc] init]; [self setWantsLayer:YES]; [self setLayer:_rootLayer]; - [_hourHandLayer setNeedsDisplay]; - [_middleHandLayer setNeedsDisplay]; - [_secondHandLayer setNeedsDisplay]; - [_minuteHandLayer setNeedsDisplay]; + [self _initHands]; [_rootLayer addSublayer:_hourHandLayer]; [_rootLayer addSublayer:_minuteHandLayer]; - [_rootLayer addSublayer:_secondHandLayer]; - [_rootLayer addSublayer:_middleHandLayer]; + if ([_datePicker valueForThemeAttribute:@"clock-second-hand-over"]) + { + [_rootLayer addSublayer:_middleHandLayer]; + [_rootLayer addSublayer:_secondHandLayer]; + } + else + { + [_rootLayer addSublayer:_secondHandLayer]; + [_rootLayer addSublayer:_middleHandLayer]; + } + + [_rootLayer setDrawsHours:[_datePicker valueForThemeAttribute:@"clock-draws-hours"]]; [_rootLayer setNeedsDisplay]; } return self; } - -#pragma mark - -#pragma mark Layout methods - -- (void)layoutSubviews +- (void)_initHands { - if ([_datePicker datePickerStyle] == CPTextFieldAndStepperDatePickerStyle || [_datePicker datePickerStyle] == CPTextFieldDatePickerStyle) - return; + var middleHandImage = [_datePicker currentValueForThemeAttribute:@"middle-hand-image"], + hourHandImage = [_datePicker currentValueForThemeAttribute:@"hour-hand-image"], + minuteHandImage = [_datePicker currentValueForThemeAttribute:@"minute-hand-image"], + secondHandImage = [_datePicker currentValueForThemeAttribute:@"second-hand-image"]; - [super layoutSubviews]; + // If hand images are true CPImage, we have to duplicate them to avoid + // the multiple delegates bug when multiple clocks are displayed - var bounds = [self bounds], - dateValue = [[_datePicker dateValue] copy]; + if ([middleHandImage isKindOfClass:[CPImage class]]) + middleHandImage = [middleHandImage duplicate]; - [dateValue _dateWithTimeZone:[_datePicker timeZone]]; + if ([hourHandImage isKindOfClass:[CPImage class]]) + hourHandImage = [hourHandImage duplicate]; - [self setBackgroundColor:[_datePicker currentValueForThemeAttribute:@"bezel-color-clock"]]; - [_middleHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"middle-hand-image"]]; - [_hourHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"hour-hand-image"]]; - [_minuteHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"minute-hand-image"]]; - [_secondHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"second-hand-image"]]; + if ([minuteHandImage isKindOfClass:[CPImage class]]) + minuteHandImage = [minuteHandImage duplicate]; - if ([_datePicker _isAmericanFormat]) - { - if (dateValue.getHours() > 11) - [_PMAMTextField setStringValue:@"PM"]; - else - [_PMAMTextField setStringValue:@"AM"]; + if ([secondHandImage isKindOfClass:[CPImage class]]) + secondHandImage = [secondHandImage duplicate]; - [_PMAMTextField sizeToFit]; - [_PMAMTextField setFrameOrigin:CGPointMake(bounds.size.width / 2 - [_PMAMTextField frameSize].width / 2, bounds.size.height / 2 + 15)]; - [_PMAMTextField setHidden:NO]; - } - else - { - [_PMAMTextField setHidden:YES]; - } + [_middleHandLayer setImage:middleHandImage]; + [_hourHandLayer setImage:hourHandImage]; + [_minuteHandLayer setImage:minuteHandImage]; + [_secondHandLayer setImage:secondHandImage]; - [_hourHandLayer setRotationRadians:[self _hourPositionRadianForDate:dateValue]]; - [_minuteHandLayer setRotationRadians:[self _minutePositionRadianForDate:dateValue]]; - [_secondHandLayer setRotationRadians:[self _secondPositionRadianForDate:dateValue]]; + [_hourHandLayer setNeedsDisplay]; + [_middleHandLayer setNeedsDisplay]; + [_secondHandLayer setNeedsDisplay]; + [_minuteHandLayer setNeedsDisplay]; - [_PMAMTextField setEnabled:_isEnabled]; - [_hourHandLayer setEnabled:_isEnabled]; - [_middleHandLayer setEnabled:_isEnabled]; - [_secondHandLayer setEnabled:_isEnabled]; - [_minuteHandLayer setEnabled:_isEnabled]; - - // Check if we have to display the hand second - if (([_datePicker datePickerElements] & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag) - [_secondHandLayer setHidden:NO]; - else - [_secondHandLayer setHidden:YES]; + [_rootLayer setFont:[_datePicker currentValueForThemeAttribute:@"clock-hours-font"]]; + [_rootLayer setTextColor:[_datePicker currentValueForThemeAttribute:@"clock-hours-text-color"]]; + [_rootLayer setRadius:[_datePicker currentValueForThemeAttribute:@"clock-hours-radius"]]; [_rootLayer setNeedsDisplay]; } +- (void)setDatePickerElements:(CPInteger)aDatePickerElements +{ + _datePickerElements = aDatePickerElements; + + // Check if we have to display the hand second + // FIXME: Don't know why but next line will cause theme compilation to fail... + // Workaround: added "if PLATFORM(DOM)" +#if PLATFORM(DOM) + [_secondHandLayer setHidden:!((_datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag)]; +#endif +} + +#pragma mark Layout methods + +- (void)layoutSubviews +{ + // While tracking a hand, we don't want the whole thing to be relayouted at each mouse movement + if (_trackingHand) + return; + + [self setBackgroundColor:[_datePicker currentValueForThemeAttribute:@"bezel-color-clock"]]; + + var dateValue = [[_datePicker dateValue] copy]; + + [dateValue _dateWithTimeZone:[_datePicker timeZone]]; + + _representedHours = dateValue.getHours(); + _representedMinutes = dateValue.getMinutes(); + _representedSeconds = dateValue.getSeconds(); + _representedHourIsPM = (_representedHours > 11); + + // Hours are expressed in 24 hours format, we need 12 hours format + _representedHours -= (_representedHourIsPM ? 12 : 0); + + [self _updateHands]; + + // FIXME: Workaround. Seems that CALayer doesn't redraw without an event + [CALayer runLoopUpdateLayers]; +} + +- (void)_updateHands +{ + var bounds = [self bounds]; + + [_PMAMTextField setStringValue:_representedHourIsPM ? @"PM" : @"AM"]; + [_PMAMTextField sizeToFit]; + [_PMAMTextField setFrameOrigin:CGPointMake(bounds.size.width / 2 - [_PMAMTextField frameSize].width / 2, bounds.size.height / 2 + 15)]; + + [_hourHandLayer setRotationRadians:(360 * (_representedHours + _representedMinutes / 60) / 12) * RADIANS]; + [_minuteHandLayer setRotationRadians:(360 * (_representedMinutes + _representedSeconds / 60) / 60) * RADIANS]; + [_secondHandLayer setRotationRadians:(360 * _representedSeconds / 60) * RADIANS]; + + [_hourHandLayer setNeedsDisplay]; + [_minuteHandLayer setNeedsDisplay]; + [_secondHandLayer setNeedsDisplay]; +// [_middleHandLayer setNeedsDisplay]; +} -#pragma mark - #pragma mark Accessors -- (float)_hourPositionRadianForDate:(CPDate)aDate -{ - var hours = aDate.getHours() + aDate.getMinutes() / 60; - - return (360 * hours / 12) * RADIANS; -} - -- (float)_secondPositionRadianForDate:(CPDate)aDate -{ - return (360 * aDate.getSeconds() / 60) * RADIANS; -} - -- (float)_minutePositionRadianForDate:(CPDate)aDate -{ - var minutes = aDate.getMinutes() + aDate.getSeconds() / 60; - - return (360 * minutes / 60) * RADIANS; -} - - (void)setEnabled:(BOOL)shouldEnable { shouldEnable = !!shouldEnable; @@ -205,36 +256,191 @@ var RADIANS = Math.PI / 180; return; _isEnabled = shouldEnable; - [self setNeedsLayout]; - // FIXME: This is a workaround for an apparent bug in CALayer. - // Without pumping the event loop, the sublayers of _rootLayer - // (the hands) are not redrawn until an event occurs. - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + [self _initHands]; + [self setNeedsLayout]; +} + +#pragma mark Mouse actions + +- (void)mouseDown:(CPEvent)anEvent +{ + if (!_isEnabled) + return; + + var currentLocation = [self convertPoint:[anEvent locationInWindow] fromView:nil]; + + if ([_secondHandLayer handIsHitAtPoint:currentLocation]) + { + _currentHandLayer = _secondHandLayer; + _currentHand = _CPDatePickerClockSeconds; + _currentRepresentedValue = _representedSeconds; + _currentValueShift = 0; + _numberOfUnits = 60; + } + else if ([_minuteHandLayer handIsHitAtPoint:currentLocation]) + { + _currentHandLayer = _minuteHandLayer; + _currentHand = _CPDatePickerClockMinutes; + _currentRepresentedValue = _representedMinutes; + _currentValueShift = _representedSeconds / 60; + _numberOfUnits = 60; + } + else if ([_hourHandLayer handIsHitAtPoint:currentLocation]) + { + _currentHandLayer = _hourHandLayer; + _currentHand = _CPDatePickerClockHours; + _currentRepresentedValue = _representedHours; + _currentValueShift = _representedMinutes / 60; + _numberOfUnits = 12; + } + else + { + _currentHandLayer = nil; + _currentHand = CPNotFound; + _currentRepresentedValue = CPNotFound; + _currentValueShift = CPNotFound; + _numberOfUnits = CPNotFound; + } + + if (_currentHandLayer) + [self trackMouse:anEvent]; +} + +- (BOOL)tracksMouseOutsideOfFrame +{ + return YES; +} + +- (BOOL)startTrackingAt:(CGPoint)aPoint +{ + _trackingHand = YES; + + return YES; +} + +- (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint +{ + var dx = aPoint.x -_bounds.size.width / 2, + dy = _bounds.size.height / 2 - aPoint.y, + angle = (PI_2 - ATAN2(dy,dx) + PI2) % PI2, + value = ROUND(angle * _numberOfUnits / PI2 - _currentValueShift) % _numberOfUnits; + + if (value !== _currentRepresentedValue) + { + var movedForward = (_currentRepresentedValue > _numberOfUnits * 3/4) && (value < _numberOfUnits / 4), + movedBackward = (_currentRepresentedValue < _numberOfUnits / 4) && (value > _numberOfUnits * 3/4), + dateValue = [[_datePicker dateValue] copy]; + + [dateValue _dateWithTimeZone:[_datePicker timeZone]]; + + switch (_currentHand) + { + case _CPDatePickerClockHours: + + if (movedForward || movedBackward) + { + _representedHourIsPM = !_representedHourIsPM; + + if (movedForward && !_representedHourIsPM) + // Day++ + dateValue.setDate(dateValue.getDate() + 1); + + else if (movedBackward && _representedHourIsPM) + // Day-- + dateValue.setDate(dateValue.getDate() - 1); + } + + dateValue.setHours(value + (_representedHourIsPM ? 12 : 0)); + break; + + case _CPDatePickerClockMinutes: + + if (movedForward) + // Hours++ + dateValue.setHours(dateValue.getHours() + 1); + + else if (movedBackward) + // Hours-- + dateValue.setHours(dateValue.getHours() - 1); + + dateValue.setMinutes(value); + break; + + case _CPDatePickerClockSeconds: + + if (movedForward) + // Minutes++ + dateValue.setMinutes(dateValue.getMinutes() + 1); + + else if (movedBackward) + // Minutes-- + dateValue.setMinutes(dateValue.getMinutes() - 1); + + dateValue.setSeconds(value); + break; + } + +#if PLATFORM(DOM) + _datePicker._invokedByUserEvent = YES; +#endif + [_datePicker _setDateValue:dateValue timeInterval:[_datePicker timeInterval]]; +#if PLATFORM(DOM) + _datePicker._invokedByUserEvent = NO; +#endif + + // We have to adapt represented values + _representedHours = dateValue.getHours(); + _representedMinutes = dateValue.getMinutes(); + _representedSeconds = dateValue.getSeconds(); + _representedHourIsPM = (_representedHours > 11); + + // Hours are expressed in 24 hours format, we need 12 hours format + _representedHours -= (_representedHourIsPM ? 12 : 0); + + switch (_currentHand) { + case _CPDatePickerClockHours: + _currentRepresentedValue = _representedHours; + break; + + case _CPDatePickerClockMinutes: + _currentRepresentedValue = _representedMinutes; + break; + + case _CPDatePickerClockSeconds: + _currentRepresentedValue = _representedSeconds; + break; + } + + [self _updateHands]; + } + + return YES; +} + +- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp +{ + _trackingHand = NO; } @end +#pragma mark - @implementation HandLayer : CALayer { - BOOL _isEnabled @accessors(setter=setEnabled:, getter=isEnabled); - - CPImage _image; - CALayer _imageLayer; - float _rotationRadians; + CPImage _image; + HandImageLayer _imageLayer; + float _rotationRadians; } - -#pragma mark - #pragma mark Init methods - (id)initWithSize:(CGSize)aSize { if (self = [super init]) { - _isEnabled = YES; - _imageLayer = [CALayer layer]; + _imageLayer = [HandImageLayer layer]; _rotationRadians = 0; [_imageLayer setDelegate:self]; @@ -247,7 +453,6 @@ var RADIANS = Math.PI / 180; } -#pragma mark - #pragma mark Setter Getter methods /*! @@ -285,18 +490,6 @@ var RADIANS = Math.PI / 180; 1.0, 1.0)]; } -- (void)setEnabled:(BOOL)shouldEnable -{ - shouldEnable = !!shouldEnable; - - if (_isEnabled === shouldEnable) - return; - - _isEnabled = shouldEnable; - [self setNeedsDisplay]; - [_imageLayer setNeedsDisplay]; -} - - (void)imageDidLoad:(CPImage)anImage { [_imageLayer setNeedsDisplay]; @@ -310,4 +503,109 @@ var RADIANS = Math.PI / 180; CGContextDrawImage(aContext, [aLayer bounds], _image); } +- (BOOL)handIsHitAtPoint:(CGPoint)aPoint +{ + return (!_isHidden && [_imageLayer hitTest:aPoint] === _imageLayer); +} + +- (void)setNeedsDisplay +{ + [super setNeedsDisplay]; + [_imageLayer setNeedsDisplay]; +} + +@end + +#pragma mark - + +@implementation HandImageLayer : CALayer +{ + CGRect _handBounds; +} + +// We have to adapt hitTest so it only takes the hand into account (that's the top half of the image layer) +// We are also sure there's no sublayers +- (CALayer)hitTest:(CGPoint)aPoint +{ + if (_isHidden) + return nil; + + var point = CGPointApplyAffineTransform(aPoint, _transformToLayer); + + return CGRectContainsPoint(_handBounds, point) ? self : nil; +} + +- (void)setBounds:(CGRect)aBounds +{ + if (CGRectEqualToRect(_bounds, aBounds)) + return; + + _handBounds = CGRectMakeCopy(aBounds); + + _handBounds.size.height = _handBounds.size.height / 2; + + [super setBounds:aBounds]; +} + + +@end + +#pragma mark - + +@implementation HoursLayer : CALayer +{ + BOOL _drawsHours; + + CPFont _font @accessors(property=font); + CPColor _textColor @accessors(property=textColor); + float _radius @accessors(property=radius); +} + + +- (id)init +{ + if (self = [super init]) + { + _drawsHours = NO; + } + + return self; +} + +- (void)setDrawsHours:(BOOL)shouldDrawHours +{ + shouldDrawHours = !!shouldDrawHours; + + if (_drawsHours === shouldDrawHours) + return; + + _drawsHours = shouldDrawHours; + [self setNeedsDisplay]; +} + +- (void)drawInContext:(CGContext)aContext +{ + [super drawInContext:aContext]; + + if (_drawsHours) + { + var bounds = [self bounds], + centerX = bounds.size.width / 2, + centerY = bounds.size.height / 2; + + CGContextSelectFont(aContext, _font); + CGContextSetFillColor(aContext, _textColor); + aContext.textBaseline = @"middle"; + aContext.textAlign = @"center"; + + for (var i = 1, angle = 60.0, x, y; i < 13; i++, angle -= 30.0) + { + x = centerX + _radius * COS(angle * RADIANS); + y = centerY - _radius * SIN(angle * RADIANS); + + aContext.fillText(i, x, y); + } + } +} + @end diff --git a/AppKit/CPDatePicker/_CPDatePickerDayView.j b/AppKit/CPDatePicker/_CPDatePickerDayView.j new file mode 100644 index 000000000..bc8cf73ce --- /dev/null +++ b/AppKit/CPDatePicker/_CPDatePickerDayView.j @@ -0,0 +1,280 @@ +/* _CPDatePickerDayView.j +* AppKit +* +* Created by Alexandre Wilhelm +* Copyright 2012 +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +@import + +@import "_CPDatePickerDayViewTextField.j" + +@class CPDatePicker + +@implementation _CPDatePickerDayView : CPControl +{ + CPDate _date @accessors(property=date); + + BOOL _isDisabled; + BOOL _isHighlighted; + BOOL _isSelected; + CPDatePicker _datePicker; + CPTextField _textField; + + CPInteger _dayInWeek @accessors(property=dayInWeek); + BOOL _firstSelected @accessors(property=firstSelected); + BOOL _lastSelected @accessors(property=lastSelected); +} + + +#pragma mark Init methods + +/*! Create a new instance of _CPDatePickerDayView + @param aFrame + @param aDatePicker + @return a new instance of _CPDatePickerDayView +*/ +- (id)initWithFrame:(CGRect)aFrame withDatePicker:(CPDatePicker)aDatePicker +{ + if (self = [super initWithFrame:aFrame]) + { + [self setHitTests:NO]; + + _datePicker = aDatePicker; + + // FIXME: Beginning with Aristo3, the text field is directly themed based on a new class _CPDatePickerDayViewTextField + if ([self isCSSBased]) + { + _textField = [[_CPDatePickerDayViewTextField alloc] initWithFrame:aFrame]; + } + else + { + _textField = [[CPTextField alloc] initWithFrame:aFrame]; + + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-alignment"] forThemeAttribute:@"alignment"]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-vertical-alignment"] forThemeAttribute:@"vertical-alignment"]; + + var contentInset = [_datePicker valueForThemeAttribute:@"tile-content-inset"]; + + if (contentInset) + [_textField setValue:contentInset forThemeAttribute:@"content-inset"]; + + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; + + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateSelected] forThemeAttribute:@"font" inState:CPThemeStateSelected]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateSelected] forThemeAttribute:@"text-color" inState:CPThemeStateSelected]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateSelected] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateSelected]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateSelected] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateSelected]; + + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; + + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateSelected]]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]]forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateSelected]]; + + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateHighlighted] forThemeAttribute:@"font" inState:CPThemeStateHighlighted]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-color" inState:CPThemeStateHighlighted]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateHighlighted]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateHighlighted]; + + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]]; + + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]]; + + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]]; + [_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]]; + } + + [self addSubview:_textField]; + + [self setNeedsLayout]; + } + + return self; +} + + +#pragma mark - +#pragma mark Theme methods + +/*! Set a theme +*/ +- (BOOL)setThemeState:(ThemeState)aState +{ + [_textField setThemeState:aState]; + [super setThemeState:aState]; +} + +/*! Unset a theme +*/ +- (BOOL)unsetThemeState:(ThemeState)aState +{ + [_textField unsetThemeState:aState]; + [super unsetThemeState:aState]; +} + + +#pragma mark - +#pragma mark Getter methods + +/*! Select the tile +*/ +- (void)setSelected:(BOOL)shouldBeSelected +{ + if (_isSelected === shouldBeSelected) + return; + + _isSelected = shouldBeSelected; + + if (_isSelected) + [self setThemeState:CPThemeStateSelected]; + else + [self unsetThemeState:CPThemeStateSelected]; +} + +/*! Disabled the tile (used for previous and next month tile) +*/ +- (void)setDisabled:(BOOL)shouldBeDisabled +{ + if (_isDisabled === shouldBeDisabled) + return; + + _isDisabled = shouldBeDisabled; + + if (_isDisabled) + [self setThemeState:CPThemeStateDisabled]; + else + [self unsetThemeState:CPThemeStateDisabled]; +} + +- (BOOL)isDisabled +{ + return _isDisabled; +} + +/*! Highlight the tile (used for current day) +*/ +- (void)setHighlighted:(BOOL)shouldBeHighlighted +{ + if (_isHighlighted === shouldBeHighlighted) + return; + + _isHighlighted = shouldBeHighlighted; + + if (_isHighlighted) + [self setThemeState:CPThemeStateHighlighted]; + else + [self unsetThemeState:CPThemeStateHighlighted]; +} + +/*! Set the stringValue of the tile + @param aStringValue +*/ +- (void)setStringValue:(CPString)aStringValue +{ + [_textField setStringValue:aStringValue]; +} + + +#pragma mark - +#pragma mark Layout methods + +/*! Layout the subviews +*/ +- (void)layoutSubviews +{ + if ([_datePicker isCSSBased]) + { + var attributeName = @"bezel-color-calendar"; + + if (_isSelected) + { + if (_firstSelected && !_lastSelected) + attributeName = @"bezel-color-calendar-left"; + else if (_lastSelected && !_firstSelected) + attributeName = @"bezel-color-calendar-right"; + else if (!_firstSelected && !_lastSelected) + { + if (_dayInWeek == 0) + attributeName = @"bezel-color-calendar-left"; + else if (_dayInWeek == 6) + attributeName = @"bezel-color-calendar-right"; + else + attributeName = @"bezel-color-calendar-middle"; + } + } + + [self setBackgroundColor:[_datePicker valueForThemeAttribute:attributeName inState:[self themeState]]]; + return; + } + + var bounds = [self bounds]; + [_textField sizeToFit]; + [_textField setFrameOrigin:CGPointMake(bounds.size.width / 2 - [_textField frameSize].width / 2 + [_datePicker valueForThemeAttribute:@"border-width"], bounds.size.height / 2 - [_textField frameSize].height / 2)]; +} + +- (void)setFrame:(CGRect)aFrame +{ + [super setFrame:aFrame]; + [_textField setFrame:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)]; +} + +/*! Drawrect +*/ +- (void)drawRect:(CGRect)aRect +{ + [super drawRect:aRect]; + + if ([_datePicker isCSSBased]) + return; + + var themeState = [self themeState], + context = [[CPGraphicsContext currentContext] graphicsPort]; + + if (themeState.hasThemeState(CPThemeStateSelected)) + { + [self setBackgroundColor:[_datePicker valueForThemeAttribute:@"bezel-color-calendar" inState:themeState]]; + CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]); + CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:themeState]); + CGContextAddRect(context, [self bounds]); + CGContextStrokeRect(context, [self bounds]); + } + else + { + // Clear color, because the original color of a tile is handle by his superview + [self setBackgroundColor:[CPColor clearColor]]; + } + +} + +@end + diff --git a/AppKit/CPDatePicker/_CPDatePickerDayViewTextField.j b/AppKit/CPDatePicker/_CPDatePickerDayViewTextField.j new file mode 100644 index 000000000..89bfb75dc --- /dev/null +++ b/AppKit/CPDatePicker/_CPDatePickerDayViewTextField.j @@ -0,0 +1,48 @@ +/* _CPDatePickerDayViewTextField.j +* AppKit +* +* Created by Alexandre Wilhelm +* Copyright 2012 +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +@import + +@import "CPTextField.j" + +@implementation _CPDatePickerDayViewTextField : CPTextField + ++ (CPString)defaultThemeClass +{ + return @"datePickerDayViewTextField"; +} + ++ (CPDictionary)themeAttributes +{ + return @{ + @"min-size": CGSizeMakeZero(), + @"content-inset": CGInsetMakeZero(), + @"text-color": [CPColor blackColor], + @"text-shadow-color": [CPColor clearColor], + @"text-shadow-offset": CGSizeMakeZero(), + @"font": [CPNull null], + @"vertical-alignment": CPCenterVerticalTextAlignment, + @"alignment": CPCenterTextAlignment, + }; +} + +@end + diff --git a/AppKit/CPDatePicker/_CPDatePickerElementTextField.j b/AppKit/CPDatePicker/_CPDatePickerElementTextField.j new file mode 100644 index 000000000..012a7a81a --- /dev/null +++ b/AppKit/CPDatePicker/_CPDatePickerElementTextField.j @@ -0,0 +1,577 @@ +/* _CPDatePickerElementTextField.j +* AppKit +* +* Created by Alexandre Wilhelm +* Copyright 2012 +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +@import "CPTextField.j" + +@class CPDatePicker +@class _CPDatePickerElementView + +CPDatePickerElementTextFieldBecomeFirstResponder = @"CPDatePickerElementTextFieldBecomeFirstResponder"; +CPDatePickerElementTextFieldAMPMChangedNotification = @"CPDatePickerElementTextFieldAMPMChangedNotification"; + +var CPZeroKeyCode = 48, + CPNineKeyCode = 57, + CPMajAKeyCode = 65, + CPMajPKeyCode = 80, + CPAKeyCode = 97, + CPPKeyCode = 112; + +CPMonthDateType = 0; +CPDayDateType = 1; +CPYearDateType = 2; +CPHourDateType = 3; +CPSecondDateType = 4; +CPMinuteDateType = 5; +CPAMPMDateType = 6; + +/*! An element textField +*/ +@implementation _CPDatePickerElementTextField : CPTextField +{ + _CPDatePickerElementTextField _nextTextField @accessors(property=nextTextField); + _CPDatePickerElementTextField _previousTextField @accessors(property=previousTextField); + _CPDatePickerElementView _datePickerElementView @accessors(property=datePickerElementView); + + CPDatePicker _datePicker @accessors(setter=setDatePicker:); + + int _dateType @accessors(getter=dateType); + int _maxNumber @accessors(getter=maxNumber); + int _minNumber @accessors(getter=minNumber); + + BOOL _firstEvent; + CPTimer _timerEdition; +} + ++ (CPString)defaultThemeClass +{ + return @"datePickerElementTextField"; +} + ++ (CPDictionary)themeAttributes +{ + return @{ + @"content-inset": CGInsetMake(1.0, 0.0, 0.0, 0.0), + @"bezel-color": [CPNull null], + @"min-size": CGSizeMakeZero() + }; +} + +- (id)init +{ + if (self = [super init]) + { + _firstEvent = YES; + } + + return self; +} + +/*! @ignore */ +- (BOOL)acceptsFirstResponder +{ + return [_datePicker isEnabled]; +} + +/*! Set the dateType of the textField +*/ +- (void)setDateType:(int)aDateType +{ + _dateType = aDateType; + + switch (aDateType) + { + case CPMonthDateType: + _minNumber = 1; + _maxNumber = 12; + break; + + case CPDayDateType: + _minNumber = 1; + _maxNumber = 31; + break; + + case CPYearDateType: + _minNumber = 0; + _maxNumber = 9999; + break; + + case CPHourDateType: + _minNumber = 0; + _maxNumber = 23; + break; + + case CPSecondDateType: + _minNumber = 0; + _maxNumber = 59; + break; + + case CPMinuteDateType: + _minNumber = 0; + _maxNumber = 59; + break; + } +} + +/*! Return the maxNumber of the textField +*/ +- (int)maxNumber +{ + if (_dateType == CPDayDateType) + return [[_datePicker dateValue] _daysInMonth]; + + return _maxNumber; +} + +/*! Return the maxNumber of the textField depending of the maxDate +*/ +- (int)_maxNumberWithMaxDate +{ + var maxDate = [_datePicker maxDate], + date = [_datePicker dateValue]; + + if (maxDate) + { + switch (_dateType) + { + case CPMonthDateType: + if (maxDate.getFullYear() == date.getFullYear()) + return maxDate.getMonth(); + break; + + case CPDayDateType: + if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth()) + return maxDate.getDate(); + break; + + case CPYearDateType: + return maxDate.getFullYear(); + + case CPHourDateType: + if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth() && maxDate.getDate() == date.getDate()) + return maxDate.getHours(); + break; + + case CPSecondDateType: + if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth() && maxDate.getDate() == date.getDate() && maxDate.getHours() == date.getHours() && maxDate.getMinutes() == date.getMinutes()) + return maxDate.getSeconds(); + break; + + case CPMinuteDateType: + if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth() && maxDate.getDate() == date.getDate() && maxDate.getHours() == date.getHours()) + return maxDate.getMinutes(); + break; + } + } + + return _maxNumber; +} + +/*! Set the stringValue of the textField. This is going to check if there is 2 or 4 letters. If not it adds a space. Check also the maxDate + It's called when the user is editing with the keyboard + @param aStringValue a CPString +*/ +- (void)setValueForKeyEvent:(CPEvent)anEvent +{ + var keyCode = [anEvent keyCode]; + + if (keyCode != CPDeleteKeyCode && keyCode != CPDeleteForwardKeyCode && keyCode < CPZeroKeyCode || keyCode > CPNineKeyCode) + return; + + var newValue = [self stringValue].replace(/\s/g, ''), + length = [newValue length], + eventKeyValue = parseInt([anEvent characters]).toString(); + + if (keyCode == CPDeleteKeyCode || keyCode == CPDeleteForwardKeyCode) + { + [_timerEdition invalidate]; + _timerEdition = nil; + newValue = [newValue substringToIndex:(length - 1)]; + } + else + { + if (!_timerEdition) + { + _timerEdition = [CPTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(_timerKeyEvent:) userInfo:nil repeats:NO]; + + if (_firstEvent || !length) + newValue = eventKeyValue; + else + newValue = parseInt(newValue).toString() + eventKeyValue; + } + else + { + var newFireDate = [CPDate date]; + + newFireDate.setSeconds(newFireDate.getSeconds() + 2); + [_timerEdition setFireDate:newFireDate]; + + newValue = parseInt(newValue).toString() + eventKeyValue; + } + } + + if (parseInt(newValue) > [self _maxNumberWithMaxDate] || ([_datePicker _isAmericanFormat] && _dateType == CPHourDateType && parseInt(newValue) > 12)) + return; + + _firstEvent = NO; + + [super setObjectValue:newValue]; +} + +/*! + End of the timer +*/ +- (void)_timerKeyEvent:(id)sender +{ + var stringValue = [self stringValue]; + + _timerEdition = nil; + + if ([stringValue length]) + { + if ([_datePicker _isAmericanFormat] && [self dateType] == CPHourDateType) + { + var isAMHour = [[self superview] _isAMHour]; + + if (!isAMHour && stringValue != 12) + stringValue = parseInt(stringValue) + 12; + + if (stringValue == 12 && !isAMHour) + stringValue = 12; + else if (stringValue == 12) + stringValue = 0; + } + + [self setObjectValue:stringValue]; + } +} + +/*! + We force to end the timer +*/ +- (void)_invalidTimer +{ + if (_timerEdition) + { + [_timerEdition invalidate]; + _timerEdition = nil; + } +} + +/*! + We force to end the timer and to update the objectValue of the datePicker +*/ +- (void)_endEditing +{ + if (_timerEdition) + [_timerEdition invalidate]; + + _timerEdition = nil; + + var objectValue = [self stringValue]; + + if (![objectValue length]) + objectValue = [self objectValue]; + + if ([_datePicker _isAmericanFormat] && [self dateType] == CPHourDateType) + { + var isAMHour = [[self superview] _isAMHour]; + + if (!isAMHour && objectValue != 12) + objectValue = parseInt(objectValue) + 12; + + if (objectValue == 12 && !isAMHour) + objectValue = 12; + else if (objectValue == 12) + objectValue = 0; + } + + [self setObjectValue:objectValue]; +} + +/*! Set the stringValue of the TextField. Add some zeros of there isn't 2/4 letters in the value. It's called at the end of the editing process + @param aStringValue a CPString +*/ +- (void)setStringValue:(CPString)aStringValue +{ + if (_dateType == CPYearDateType) + { + while ([aStringValue length] < 4) + aStringValue = "0" + aStringValue; + } + else if (_dateType != CPAMPMDateType) + { + if (_dateType == CPHourDateType && [_datePicker _isAmericanFormat]) + { + var value = parseInt(aStringValue); + + if (value == 0) + value = 12; + else if (value > 12) + value = value - 12; + + aStringValue = value.toString(); + } + + while ([aStringValue length] < 2) + { + if (_dateType == CPSecondDateType || _dateType == CPMinuteDateType) + aStringValue = @"0" + aStringValue; + else + aStringValue = @" " + aStringValue; + } + + } + + [super setObjectValue:aStringValue]; +} + +/*! Set the objectValue. This will update the dateValue of the datePicker also. It's called with the binding of the stepper or arrows + This is not going to update the objectValue of the control !!! It updates the dateValue of the datePicker who's going to update the datePickerTextField if necessary + It's a bit tricky + @param aObjectValue +*/ +- (void)setObjectValue:(id)anObjectValue +{ + var dateValue = [[_datePicker dateValue] copy], + lengthString = [[self stringValue] length], + objectValue = parseInt(anObjectValue); + + switch (_dateType) + { + case CPMonthDateType: + + if (objectValue == 0 || !lengthString) + { + [self setStringValue:(dateValue.getMonth() + 1).toString()]; + return; + } + + var dateNextMonth = [dateValue copy]; + + dateNextMonth.setDate(1); + dateNextMonth.setMonth(parseInt(anObjectValue) - 1); + + var numberDayNextMonth = [dateNextMonth _daysInMonth]; + + if (numberDayNextMonth < [dateValue _daysInMonth] && dateValue.getDate() > numberDayNextMonth) + [_datePickerElementView setDayDateValue:numberDayNextMonth.toString()]; + + [super setObjectValue:objectValue]; + break; + + case CPDayDateType: + + if (objectValue == 0 || !lengthString) + { + [self setStringValue:dateValue.getDate().toString()]; + return; + } + + [super setObjectValue:objectValue]; + break; + + case CPYearDateType: + + if (objectValue == 0 || !lengthString) + { + [self setStringValue:dateValue.getFullYear().toString()]; + return; + } + + [super setObjectValue:objectValue]; + break; + + case CPHourDateType: + + if (!lengthString) + { + [self setStringValue:dateValue.getHours().toString()]; + return; + } + + [super setObjectValue:objectValue]; + break; + + case CPSecondDateType: + + if (!lengthString) + { + [self setStringValue:dateValue.getSeconds().toString()]; + return; + } + + [super setObjectValue:objectValue]; + break; + + case CPMinuteDateType: + + if (!lengthString) + { + [self setStringValue:dateValue.getMinutes().toString()]; + return; + } + + [super setObjectValue:objectValue]; + break; + } + + var newDateValue = [_datePickerElementView dateValue], + timeZone = [_datePicker timeZone]; + + if (timeZone) + { + var secondsFromGMT = [timeZone secondsFromGMTForDate:newDateValue], + secondsFromGMTTimeZone = [timeZone secondsFromGMT]; + + newDateValue.setSeconds(newDateValue.getSeconds() + secondsFromGMT - secondsFromGMTTimeZone); + } + +#if PLATFORM(DOM) + _datePicker._invokedByUserEvent = YES; +#endif + [_datePicker _setDateValue:newDateValue timeInterval:[_datePicker timeInterval]]; +#if PLATFORM(DOM) + _datePicker._invokedByUserEvent = NO; +#endif +} + + +#pragma mark - +#pragma mark Mouse event + +/*! Mouse down event. Launch a notification to notif the new first responder textField +*/ +- (void)mouseDown:(CPEvent)anEvent +{ + if (![self isEnabled]) + return; + + [super mouseDown:anEvent]; + [[CPNotificationCenter defaultCenter] postNotificationName:CPDatePickerElementTextFieldBecomeFirstResponder object:[[self superview] superview] userInfo:[CPDictionary dictionaryWithObject:self forKey:@"textField"]]; +} + + +#pragma mark - +#pragma mark Theme functions + +/*! Set the theme CPThemeStateSelected +*/ +- (void)makeSelectable +{ + [self setThemeState:CPThemeStateSelected]; + [_datePicker setThemeState:CPThemeStateEditing]; +} + +/*! Unsert the theme CPThemeStateSelected +*/ +- (void)makeDeselectable +{ + _firstEvent = YES; + [self unsetThemeState:CPThemeStateSelected]; + [_datePicker unsetThemeState:CPThemeStateEditing]; +} + + +#pragma mark - +#pragma mark Override + +/*! + We override this method to get all the time the good width +*/ +- (CGSize)_minimumFrameSize +{ + var frameSize = [self frameSize], + contentInset = [self currentValueForThemeAttribute:@"content-inset"], + minSize = [self currentValueForThemeAttribute:@"min-size"], + maxSize = [self currentValueForThemeAttribute:@"max-size"], + lineBreakMode = [self lineBreakMode], + text = (_dateType == CPYearDateType) ? @"0000" : (_dateType == CPMonthDateType) ? @"10" : @"00", + textSize = CGSizeMakeCopy(frameSize), + font = [self currentValueForThemeAttribute:@"font"]; + + textSize.width -= contentInset.left + contentInset.right; + textSize.height -= contentInset.top + contentInset.bottom; + + if (_dateType == CPAMPMDateType) + text = [self stringValue]; + + if (frameSize.width !== 0 && + ![self isBezeled] && + (lineBreakMode === CPLineBreakByWordWrapping || lineBreakMode === CPLineBreakByCharWrapping)) + { + textSize = [text sizeWithFont:font inWidth:textSize.width]; + } + else + { + textSize = [text sizeWithFont:font]; + + // Account for possible fractional pixels at right edge + textSize.width += 1; + } + + // Account for possible fractional pixels at bottom edge + textSize.height += 1; + + frameSize.height = textSize.height + contentInset.top + contentInset.bottom; + + if ([self isBezeled]) + { + frameSize.height = MAX(frameSize.height, minSize.height); + + if (maxSize.width > 0.0) + frameSize.width = MIN(frameSize.width, maxSize.width); + + if (maxSize.height > 0.0) + frameSize.height = MIN(frameSize.height, maxSize.height); + } + else + frameSize.width = textSize.width + contentInset.left + contentInset.right; + + frameSize.width = MAX(frameSize.width, minSize.width); + + return frameSize; +} + +- (CGRect)bezelRectForBounds:(CGRect)bounds +{ + return CGRectMakeCopy(bounds); +} + +@end + +#pragma mark - + +@implementation _CPDatePickerElementSeparator : CPTextField + ++ (CPString)defaultThemeClass +{ + return @"datePickerElementSeparator"; +} + ++ (CPDictionary)themeAttributes +{ + return @{ + @"content-inset": CGInsetMake(1.0, 0.0, 0.0, 0.0), + @"min-size": CGSizeMakeZero() + }; +} + +@end diff --git a/AppKit/CPDatePicker/_CPDatePickerElementView.j b/AppKit/CPDatePicker/_CPDatePickerElementView.j new file mode 100644 index 000000000..ce4c8702b --- /dev/null +++ b/AppKit/CPDatePicker/_CPDatePickerElementView.j @@ -0,0 +1,1046 @@ +/* _CPDatePickerElementView.j +* AppKit +* +* Created by Alexandre Wilhelm +* Copyright 2012 +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +@import "CPControl.j" +@import "_CPDatePickerElementTextField.j" + +@class CPDatePicker + +@global CPHourMinuteDatePickerElementFlag +@global CPHourMinuteSecondDatePickerElementFlag +@global CPTimeZoneDatePickerElementFlag +@global CPYearMonthDatePickerElementFlag +@global CPYearMonthDayDatePickerElementFlag +@global CPEraDatePickerElementFlag + +/*! This class is used to display the elements (all of the textFields) in the datePicker +*/ +@implementation _CPDatePickerElementView : CPControl +{ + _CPDatePickerElementTextField _textFieldDay; + _CPDatePickerElementTextField _textFieldMonth; + _CPDatePickerElementTextField _textFieldYear; + _CPDatePickerElementTextField _textFieldHour; + _CPDatePickerElementTextField _textFieldMinute; + _CPDatePickerElementTextField _textFieldSecond; + _CPDatePickerElementTextField _textFieldPMAM; + CPDatePicker _datePicker; + CPTextField _textFieldSeparatorOne; + CPTextField _textFieldSeparatorTwo; + CPTextField _textFieldSeparatorThree; + CPTextField _textFieldSeparatorFour; +} + + +#pragma mark Init + +- (id)initWithFrame:(CGRect)aFrame withDatePicker:(CPDatePicker)aDatePicker +{ + if (self = [super init]) + { + _datePicker = aDatePicker; + [self _init]; + } + return self; +} + +- (void)_init +{ + _textFieldDay = [_CPDatePickerElementTextField new]; + [_textFieldDay setBezeled:NO]; + [_textFieldDay setBordered:NO]; + [_textFieldDay setDateType:CPDayDateType]; + [_textFieldDay setDatePicker:_datePicker]; + [_textFieldDay setAlignment:CPRightTextAlignment]; + [_textFieldDay setDatePickerElementView:self]; + [self addSubview:_textFieldDay]; + + _textFieldMonth = [_CPDatePickerElementTextField new]; + [_textFieldMonth setBezeled:NO]; + [_textFieldMonth setBordered:NO]; + [_textFieldMonth setDateType:CPMonthDateType]; + [_textFieldMonth setDatePicker:_datePicker]; + [_textFieldMonth setAlignment:CPRightTextAlignment]; + [_textFieldMonth setDatePickerElementView:self]; + [self addSubview:_textFieldMonth]; + + _textFieldYear = [_CPDatePickerElementTextField new]; + [_textFieldYear setBezeled:NO]; + [_textFieldYear setBordered:NO]; + [_textFieldYear setDateType:CPYearDateType]; + [_textFieldYear setDatePicker:_datePicker]; + [_textFieldYear setAlignment:CPRightTextAlignment]; + [_textFieldYear setDatePickerElementView:self]; + [self addSubview:_textFieldYear]; + + _textFieldHour = [_CPDatePickerElementTextField new]; + [_textFieldHour setBezeled:NO]; + [_textFieldHour setBordered:NO]; + [_textFieldHour setDateType:CPHourDateType]; + [_textFieldHour setDatePicker:_datePicker]; + [_textFieldHour setAlignment:CPRightTextAlignment]; + [_textFieldHour setDatePickerElementView:self]; + [self addSubview:_textFieldHour]; + + _textFieldMinute = [_CPDatePickerElementTextField new]; + [_textFieldMinute setBezeled:NO]; + [_textFieldMinute setBordered:NO]; + [_textFieldMinute setDateType:CPMinuteDateType]; + [_textFieldMinute setDatePicker:_datePicker]; + [_textFieldMinute setAlignment:CPRightTextAlignment]; + [_textFieldMinute setDatePickerElementView:self]; + [self addSubview:_textFieldMinute]; + + _textFieldSecond = [_CPDatePickerElementTextField new]; + [_textFieldSecond setBezeled:NO]; + [_textFieldSecond setBordered:NO]; + [_textFieldSecond setDateType:CPSecondDateType]; + [_textFieldSecond setDatePicker:_datePicker]; + [_textFieldSecond setAlignment:CPRightTextAlignment]; + [_textFieldSecond setDatePickerElementView:self]; + [self addSubview:_textFieldSecond]; + + _textFieldPMAM = [_CPDatePickerElementTextField new]; + [_textFieldPMAM setBezeled:NO]; + [_textFieldPMAM setBordered:NO]; + [_textFieldPMAM setDateType:CPAMPMDateType]; + [_textFieldPMAM setDatePicker:_datePicker]; + [_textFieldPMAM setAlignment:CPRightTextAlignment]; + [_textFieldPMAM setDatePickerElementView:self]; + [self addSubview:_textFieldPMAM]; + + _textFieldSeparatorOne = [_CPDatePickerElementSeparator labelWithTitle:@"/"]; + _textFieldSeparatorTwo = [_CPDatePickerElementSeparator labelWithTitle:@"/"]; + _textFieldSeparatorThree = [_CPDatePickerElementSeparator labelWithTitle:@":"]; + _textFieldSeparatorFour = [_CPDatePickerElementSeparator labelWithTitle:@":"]; + + [self addSubview: _textFieldSeparatorOne]; + [self addSubview: _textFieldSeparatorTwo]; + [self addSubview: _textFieldSeparatorThree]; + [self addSubview: _textFieldSeparatorFour]; + + [self setNeedsLayout]; +} + + +#pragma mark - +#pragma mark Responder methods + +/*! @ignore */ +- (BOOL)acceptsFirstResponder +{ + // This is needed to accept to be firstResponder when nothing is selected. + // This element needs to be first responder when the CPDatePicker is in the CPTableView + // When clicking on a row of a CPTableView where there isn't a date element, the CPTableView will ask this element to know if it can becomes or not a firstResponder. + return [_datePicker isEnabled] && ![self superview]._currentTextField; +} + + +#pragma mark - +#pragma mark Override observers + +- (void)_removeObservers +{ + if (!_isObserving) + return; + + [super _removeObservers]; + + [[CPNotificationCenter defaultCenter] removeObserver:self name:CPDatePickerElementTextFieldAMPMChangedNotification object:_textFieldPMAM]; +} + +- (void)_addObservers +{ + if (_isObserving) + return; + + [super _addObservers]; + + [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_datePickerElementTextFieldAMPMChangedNotification:) name:CPDatePickerElementTextFieldAMPMChangedNotification object:_textFieldPMAM]; +} + +#pragma mark - +#pragma mark Mouse event + +- (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint +{ + [self _selectTextFieldForPoint:aPoint]; + return YES; +} + +- (BOOL)startTrackingAt:(CGPoint)aPoint +{ + [self _selectTextFieldForPoint:aPoint]; + return YES; +} + +- (void)_selectTextFieldForPoint:(CGPoint)aPoint +{ + var textField = [self _textFieldForPoint:aPoint], + superview = [self superview]; + + if (!textField || [superview._currentTextField] == textField) + return; + + [[CPNotificationCenter defaultCenter] postNotificationName:CPDatePickerElementTextFieldBecomeFirstResponder object:superview userInfo:[CPDictionary dictionaryWithObject:textField forKey:@"textField"]]; +} + +- (_CPDatePickerElementTextField)_textFieldForPoint:(CGPoint)aPoint +{ + if (![_textFieldDay isHidden] && CGRectContainsPoint([_textFieldDay frame], aPoint)) + return _textFieldDay; + + if (![_textFieldMonth isHidden] && CGRectContainsPoint([_textFieldMonth frame], aPoint)) + return _textFieldMonth; + + if (![_textFieldYear isHidden] && CGRectContainsPoint([_textFieldYear frame], aPoint)) + return _textFieldYear; + + if (![_textFieldHour isHidden] && CGRectContainsPoint([_textFieldHour frame], aPoint)) + return _textFieldHour; + + if (![_textFieldMinute isHidden] && CGRectContainsPoint([_textFieldMinute frame], aPoint)) + return _textFieldMinute; + + if (![_textFieldSecond isHidden] && CGRectContainsPoint([_textFieldSecond frame], aPoint)) + return _textFieldSecond; + + if (![_textFieldPMAM isHidden] && CGRectContainsPoint([_textFieldPMAM frame], aPoint)) + return _textFieldPMAM; + + return nil; +} + +#pragma mark - +#pragma mark Setter Getter methods + +/*! Set the value of the textFields + @param aDateValue the value +*/ +- (void)setDateValue:(CPDate)aDateValue +{ + [_textFieldDay setStringValue:[CPString stringWithFormat:@"%i", aDateValue.getDate()]]; + [_textFieldMonth setStringValue:[CPString stringWithFormat:@"%i", aDateValue.getMonth() + 1]]; + [_textFieldYear setStringValue:[CPString stringWithFormat:@"%i", aDateValue.getFullYear()]]; + [_textFieldHour setStringValue:[CPString stringWithFormat:@"%i", aDateValue.getHours()]]; + [_textFieldMinute setStringValue:[CPString stringWithFormat:@"%i", aDateValue.getMinutes()]]; + [_textFieldSecond setStringValue:[CPString stringWithFormat:@"%i", aDateValue.getSeconds()]]; + + if (aDateValue.getHours() > 11) + [_textFieldPMAM setStringValue:@"PM"]; + else + [_textFieldPMAM setStringValue:@"AM"]; +} + +/*! Set the day date value to the appropriate textField + @param aDayDateValue the day +*/ +- (void)setDayDateValue:(CPString)aDayDateValue +{ + [_textFieldDay setStringValue:aDayDateValue]; +} + +/*! Set the widget enabled or not + @param aBoolean +*/ +- (void)setEnabled:(BOOL)aBoolean +{ + [super setEnabled:aBoolean]; + [_textFieldDay setEnabled:aBoolean]; + [_textFieldMonth setEnabled:aBoolean]; + [_textFieldYear setEnabled:aBoolean]; + [_textFieldHour setEnabled:aBoolean]; + [_textFieldMinute setEnabled:aBoolean]; + [_textFieldSecond setEnabled:aBoolean]; + [_textFieldSeparatorOne setEnabled:aBoolean]; + [_textFieldSeparatorTwo setEnabled:aBoolean]; + [_textFieldSeparatorThree setEnabled:aBoolean]; + [_textFieldSeparatorFour setEnabled:aBoolean]; + [_textFieldPMAM setEnabled:aBoolean]; +} + +- (void)setTextColor:(CPColor)aColor +{ + [super setTextColor:aColor]; + + [_textFieldDay setTextColor:aColor]; + [_textFieldMonth setTextColor:aColor]; + [_textFieldYear setTextColor:aColor]; + [_textFieldHour setTextColor:aColor]; + [_textFieldMinute setTextColor:aColor]; + [_textFieldSecond setTextColor:aColor]; + [_textFieldSeparatorOne setTextColor:aColor]; + [_textFieldSeparatorTwo setTextColor:aColor]; + [_textFieldSeparatorThree setTextColor:aColor]; + [_textFieldSeparatorFour setTextColor:aColor]; + [_textFieldPMAM setTextColor:aColor]; +} + +- (void)setTextFont:(CPFont)aFont +{ + [self setFont:aFont]; + + [_textFieldDay setFont:aFont]; + [_textFieldMonth setFont:aFont]; + [_textFieldYear setFont:aFont]; + [_textFieldHour setFont:aFont]; + [_textFieldMinute setFont:aFont]; + [_textFieldSecond setFont:aFont]; + [_textFieldSeparatorOne setFont:aFont]; + [_textFieldSeparatorTwo setFont:aFont]; + [_textFieldSeparatorThree setFont:aFont]; + [_textFieldSeparatorFour setFont:aFont]; + [_textFieldPMAM setFont:aFont]; +} + +/*! Return YES if the hour is set to the morning +*/ +- (BOOL)_isAMHour +{ + return [[_textFieldPMAM stringValue] isEqualToString:@"AM"]; +} + +- (CPDate)dateValue +{ + var date = [[_datePicker dateValue] copy]; + + [date _dateWithTimeZone:[_datePicker timeZone]]; + + if (![_textFieldDay isHidden]) + date.setDate([_textFieldDay intValue]); + + if (![_textFieldMonth isHidden]) + date.setMonth([_textFieldMonth intValue] - 1); + + if (![_textFieldYear isHidden]) + date.setFullYear([_textFieldYear intValue]); + + if (![_textFieldSecond isHidden]) + date.setSeconds([_textFieldSecond intValue]); + + if (![_textFieldMinute isHidden]) + date.setMinutes([_textFieldMinute intValue]); + + if (![_textFieldHour isHidden]) + { + var hour = [_textFieldHour intValue], + currentHour = parseInt(date.getHours()); + + if (hour != currentHour) + { + if ([_datePicker _isAmericanFormat]) + { + if (![self _isAMHour]) + { + if (!(currentHour == 12 && hour == 11) && hour < 13) + hour = hour + 12; + } + else if (hour == 12 && currentHour != 11) + { + hour = 0; + } + else if (currentHour == 0 && hour == 11) + { + hour = 23; + } + else if (hour == 13) + { + hour = 1; + } + } + + if (hour == 24) + hour = 0; + + date.setHours(hour); + } + } + + return date; +} + + +#pragma mark - +#pragma mark Notification methods + +/*! Called when changing AM or PM + @param aNotification +*/ +- (void)_datePickerElementTextFieldAMPMChangedNotification:(CPNotification)aNotification +{ + var value = [[aNotification object] stringValue], + dateValue = [[_datePicker dateValue] copy], + d = [dateValue copy]; + + [d _dateWithTimeZone:[_datePicker timeZone]]; + + if ([value isEqualToString:@"PM"]) + { + if (d.getHours() <= 11) + dateValue.setHours(dateValue.getHours() + 12); + } + else + { + if (d.getHours() > 11) + dateValue.setHours(dateValue.getHours() - 12); + } + +#if PLATFORM(DOM) + _datePicker._invokedByUserEvent = YES; +#endif + [_datePicker _setDateValue:dateValue timeInterval:[_datePicker timeInterval]]; +#if PLATFORM(DOM) + _datePicker._invokedByUserEvent = NO; +#endif +} + + +#pragma mark - +#pragma mark Layout methods + +- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName +{ + if (aName === "bezel-view") + return [self bounds]; + + return [super rectForEphemeralSubviewNamed:aName]; +} + +- (CPView)createEphemeralSubviewNamed:(CPString)aName +{ + if (aName === "bezel-view") + { + var view = [[CPView alloc] initWithFrame:CGRectMakeZero()]; + + [view setHitTests:NO]; + + return view; + } + + return [super createEphemeralSubviewNamed:aName]; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + var themeState = [_datePicker themeState]; + + if ([self isCSSBased]) + { + if ([_datePicker isBezeled] || [_datePicker isBordered]) + { + var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view" + positioned:CPWindowBelow + relativeToEphemeralSubviewNamed:nil]; + + [bezelView setBackgroundColor:[_datePicker valueForThemeAttribute:@"bezel-color" inState:themeState]]; + } + + if ([_datePicker drawsBackground]) + [self setBackgroundColor:[_datePicker backgroundColor]]; + else + [self setBackgroundColor:[CPColor clearColor]]; + } + else + { + if ([_datePicker isBezeled] && [_datePicker drawsBackground] || [_datePicker isBordered] && [_datePicker drawsBackground]) + [self setBackgroundColor:[_datePicker valueForThemeAttribute:@"bezel-color" inState:themeState]]; + else if ([_datePicker drawsBackground]) + [self setBackgroundColor:[_datePicker backgroundColor]]; + else + [self setBackgroundColor:[CPColor clearColor]]; + } + + [self _themeTextFields]; + + [self _updateResponderTextField]; + [self _updateHiddenTextFields]; + [self _setControlSizes]; + [self _sizeToFit]; + [self _updatePositions]; + + [self setNeedsDisplay:YES]; +} + +- (void)drawRect:(CGRect)aRect +{ + [super drawRect:aRect]; + + if (([_datePicker isBordered] || [_datePicker isBezeled]) && ![_datePicker drawsBackground]) + { + var context = [[CPGraphicsContext currentContext] graphicsPort], + borderWidth = [_datePicker valueForThemeAttribute:@"border-width"] / 2, + bezelInset = [_datePicker valueForThemeAttribute:@"bezel-inset" inState:[_datePicker themeState]]; + + CGContextBeginPath(context); + CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:[_datePicker themeState]]); + CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]); + + CGContextMoveToPoint(context, borderWidth - bezelInset.left, borderWidth); + CGContextAddLineToPoint(context, [self bounds].size.width + bezelInset.left - borderWidth, borderWidth); + CGContextAddLineToPoint(context, [self bounds].size.width + bezelInset.left - borderWidth, [self bounds].size.height - borderWidth); + CGContextAddLineToPoint(context, borderWidth - bezelInset.left, [self bounds].size.height - borderWidth); + CGContextAddLineToPoint(context, borderWidth - bezelInset.left, borderWidth); + + CGContextStrokePath(context); + CGContextClosePath(context); + } + +} + +/*! Theme the textFields depending of the theme of the datePicker +*/ +- (void)_themeTextFields +{ + // Beginning with Aristo3, elements and separators text fields are directly themed + if ([self isCSSBased]) + return; + + var disabledTextColor = [_datePicker valueForThemeAttribute:@"text-color" inState:CPThemeStateDisabled], + disabledTextFieldBezelColor = [_datePicker valueForThemeAttribute:@"datepicker-textfield-bezel-color" inState:CPThemeStateDisabled], + disabledTextShadowColor = [_datePicker valueForThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled], + disabledTextShadowOffset = [_datePicker valueForThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled], + normalSeparatorContentInset = [_datePicker valueForThemeAttribute:@"content-inset-datepicker-textfield-separator" inState:CPThemeStateNormal], + normalTextFieldBezelColor = [_datePicker valueForThemeAttribute:@"datepicker-textfield-bezel-color" inState:CPThemeStateNormal], + normalTextFieldContentInset = [_datePicker valueForThemeAttribute:@"content-inset-datepicker-textfield" inState:CPThemeStateNormal], + normalTextShadowColor = [_datePicker valueForThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal], + normalTextShadowOffset = [_datePicker valueForThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal], + selectedSeparatorContentInset = [_datePicker valueForThemeAttribute:@"content-inset-datepicker-textfield-separator" inState:CPThemeStateSelected], + selectedTextColor = [_datePicker valueForThemeAttribute:@"text-color" inState:CPThemeStateSelected], + selectedTextFieldBezelColor = [_datePicker valueForThemeAttribute:@"datepicker-textfield-bezel-color" inState:CPThemeStateSelected], + selectedTextFieldContentInset = [_datePicker valueForThemeAttribute:@"content-inset-datepicker-textfield" inState:CPThemeStateSelected], + selectedTextShadowColor = [_datePicker valueForThemeAttribute:@"text-shadow-color" inState:CPThemeStateSelected], + selectedTextShadowOffset = [_datePicker valueForThemeAttribute:@"text-shadow-offset" inState:CPThemeStateSelected], + textColor = [_datePicker textColor], + textFieldMinSize = [_datePicker currentValueForThemeAttribute:@"min-size-datepicker-textfield"], + textFont = [_datePicker textFont]; + + [self _textFieldSetValue:textFieldMinSize forThemeAttribute:@"min-size" inState:nil]; + [self _textFieldSetValue:normalTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateNormal]; + [self _textFieldSetValue:selectedTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateSelected]; + [self _textFieldSetValue:selectedTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateSelected]; + [self _textFieldSetValue:textFont forThemeAttribute:@"font" inState:CPThemeStateSelected]; + [self _textFieldSetValue:selectedTextColor forThemeAttribute:@"text-color" inState:CPThemeStateSelected]; + [self _textFieldSetValue:selectedTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateSelected]; + [self _textFieldSetValue:selectedTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateSelected]; + [self _textFieldSetValue:normalTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateNormal]; + [self _textFieldSetValue:textFont forThemeAttribute:@"font" inState:CPThemeStateNormal]; + [self _textFieldSetValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; + [self _textFieldSetValue:normalTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; + [self _textFieldSetValue:normalTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; + [self _textFieldSetValue:disabledTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateDisabled]; + [self _textFieldSetValue:textFont forThemeAttribute:@"font" inState:CPThemeStateDisabled]; + [self _textFieldSetValue:disabledTextColor forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; + [self _textFieldSetValue:disabledTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; + [self _textFieldSetValue:disabledTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; + + [self _textFieldSeparatorSetValue:normalSeparatorContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateNormal]; + [self _textFieldSeparatorSetValue:selectedSeparatorContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateSelected]; + [self _textFieldSeparatorSetValue:textFont forThemeAttribute:@"font" inState:CPThemeStateNormal]; + [self _textFieldSeparatorSetValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; + [self _textFieldSeparatorSetValue:normalTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; + [self _textFieldSeparatorSetValue:normalTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; + [self _textFieldSeparatorSetValue:textFont forThemeAttribute:@"font" inState:CPThemeStateDisabled]; + [self _textFieldSeparatorSetValue:disabledTextColor forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; + [self _textFieldSeparatorSetValue:disabledTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; + [self _textFieldSeparatorSetValue:disabledTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; +} + +- (void)_textFieldSetValue:(id)aValue forThemeAttribute:(CPString)aThemeAttribute inState:(CPThemeState)aThemeState +{ + if (aThemeState) + { + [_textFieldDay setValue:aValue forThemeAttribute:aThemeAttribute inState:aThemeState]; + [_textFieldMonth setValue:aValue forThemeAttribute:aThemeAttribute inState:aThemeState]; + [_textFieldYear setValue:aValue forThemeAttribute:aThemeAttribute inState:aThemeState]; + [_textFieldHour setValue:aValue forThemeAttribute:aThemeAttribute inState:aThemeState]; + [_textFieldMinute setValue:aValue forThemeAttribute:aThemeAttribute inState:aThemeState]; + [_textFieldSecond setValue:aValue forThemeAttribute:aThemeAttribute inState:aThemeState]; + [_textFieldPMAM setValue:aValue forThemeAttribute:aThemeAttribute inState:aThemeState]; + } + else + { + [_textFieldDay setValue:aValue forThemeAttribute:aThemeAttribute]; + [_textFieldMonth setValue:aValue forThemeAttribute:aThemeAttribute]; + [_textFieldYear setValue:aValue forThemeAttribute:aThemeAttribute]; + [_textFieldHour setValue:aValue forThemeAttribute:aThemeAttribute]; + [_textFieldMinute setValue:aValue forThemeAttribute:aThemeAttribute]; + [_textFieldSecond setValue:aValue forThemeAttribute:aThemeAttribute]; + [_textFieldPMAM setValue:aValue forThemeAttribute:aThemeAttribute]; + } +} + +- (void)_textFieldSeparatorSetValue:(id)aValue forThemeAttribute:(CPString)aThemeAttribute inState:(CPThemeState)aThemeState +{ + [_textFieldSeparatorOne setValue:aValue forThemeAttribute:aThemeAttribute inState:aThemeState]; + [_textFieldSeparatorTwo setValue:aValue forThemeAttribute:aThemeAttribute inState:aThemeState]; + [_textFieldSeparatorThree setValue:aValue forThemeAttribute:aThemeAttribute inState:aThemeState]; + [_textFieldSeparatorFour setValue:aValue forThemeAttribute:aThemeAttribute inState:aThemeState]; + +} + +/*! Hide or not the textField depending on the datePickerElements flag +*/ +- (void)_updateHiddenTextFields +{ + var datePickerElements = [_datePicker datePickerElements], + isAmericanFormat = [_datePicker _isAmericanFormat]; + + if (datePickerElements & CPYearMonthDatePickerElementFlag) + { + [_textFieldDay setHidden:YES]; + [_textFieldMonth setHidden:NO]; + [_textFieldYear setHidden:NO]; + [_textFieldSeparatorTwo setHidden:NO]; + [_textFieldSeparatorOne setHidden:YES]; + + if (([datePickerElements & CPYearMonthDayDatePickerElementFlag]) == CPYearMonthDayDatePickerElementFlag) + { + [_textFieldDay setHidden:NO]; + [_textFieldSeparatorOne setHidden:NO]; + } + } + else + { + [_textFieldMonth setHidden:YES]; + [_textFieldYear setHidden:YES]; + [_textFieldDay setHidden:YES]; + [_textFieldSeparatorTwo setHidden:YES]; + [_textFieldSeparatorOne setHidden:YES]; + } + + if (datePickerElements & CPHourMinuteDatePickerElementFlag) + { + [_textFieldHour setHidden:NO]; + [_textFieldMinute setHidden:NO]; + [_textFieldSecond setHidden:YES]; + [_textFieldSeparatorThree setHidden:NO]; + [_textFieldSeparatorFour setHidden:YES]; + + if (isAmericanFormat) + [_textFieldPMAM setHidden:NO]; + else + [_textFieldPMAM setHidden:YES]; + + if ((datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag) + { + [_textFieldSecond setHidden:NO]; + [_textFieldSeparatorFour setHidden:NO]; + } + } + else + { + [_textFieldHour setHidden:YES]; + [_textFieldMinute setHidden:YES]; + [_textFieldSecond setHidden:YES]; + [_textFieldSeparatorThree setHidden:YES]; + [_textFieldSeparatorFour setHidden:YES]; + [_textFieldPMAM setHidden:YES]; + } +} + +/*! Update the position of the textField depending on the datePickerElements flag +*/ +- (void)_updatePositions +{ + var contentInset = [_datePicker valueForThemeAttribute:@"content-inset" inState:[_datePicker themeState]] || CGInsetMakeZero(), + separatorContentInset = [_datePicker valueForThemeAttribute:@"separator-content-inset"], + timeSeparatorContentInset = [_datePicker valueForThemeAttribute:@"time-separator-content-inset"] || separatorContentInset, + horizontalInset = contentInset.left - contentInset.right, + verticalInset = contentInset.top - contentInset.bottom, + firstTextField = _textFieldMonth, + secondTextField = _textFieldDay, + isAmericanFormat = [_datePicker _isAmericanFormat]; + + if (!isAmericanFormat) + { + firstTextField = _textFieldDay; + secondTextField = _textFieldMonth; + } + + // IMPORTANT REMARK + // The "content-inset" theme parameter was incorrectly used before Aristo3 + // While an inset should shrink a rectangle as indicated by its .top, .right, .bottom and .left components, + // it was only used to shift origin by (.top - .bottom) and (.left - .right) values. + // This leads to difficulties for the theme descriptor to correctly specify positionning. + // + // In order to ensure compatibility with previous behavior, we use isCSSBased property to + // branch between new and old behaviors as all themes before Aristo3 use the previous uncorrect computation. + + if (![self isCSSBased]) + { + [self _deprecatedUpdatePositions]; + return; + } + + // New behavior of the "content-inset" theme parameter + var currentFrameWidth, + newFrame, + currentX = contentInset.left, + height = [self frame].size.height - contentInset.top - contentInset.bottom; + + if (![firstTextField isHidden]) + { + currentFrameWidth = [firstTextField frame].size.width; + newFrame = CGRectMake(currentX, contentInset.top, currentFrameWidth, height); + currentX += currentFrameWidth; + + [firstTextField setFrame:newFrame]; + + currentFrameWidth = [_textFieldSeparatorOne frame].size.width; + currentX += separatorContentInset.left; + newFrame = CGRectMake(currentX, contentInset.top, currentFrameWidth, height); + currentX += currentFrameWidth; + + [_textFieldSeparatorOne setFrame:newFrame]; + + currentX += separatorContentInset.right; + } + + [_textFieldSeparatorOne setHidden:[firstTextField isHidden]]; + + if (![secondTextField isHidden]) + { + currentFrameWidth = [secondTextField frame].size.width; + newFrame = CGRectMake(currentX, contentInset.top, currentFrameWidth, height); + currentX += currentFrameWidth; + + [secondTextField setFrame:newFrame]; + + currentFrameWidth = [_textFieldSeparatorTwo frame].size.width; + currentX += separatorContentInset.left; + newFrame = CGRectMake(currentX, contentInset.top, currentFrameWidth, height); + currentX += currentFrameWidth; + + [_textFieldSeparatorTwo setFrame:newFrame]; + + currentX += separatorContentInset.right; + } + + [_textFieldSeparatorTwo setHidden:[secondTextField isHidden]]; + + if (![_textFieldYear isHidden]) + { + currentFrameWidth = [_textFieldYear frame].size.width; + newFrame = CGRectMake(currentX, contentInset.top, currentFrameWidth, height); + currentX += currentFrameWidth; + + [_textFieldYear setFrame:newFrame]; + + currentX += [_datePicker currentValueForThemeAttribute:@"date-hour-margin"]; + } + + if (![_textFieldHour isHidden]) + { + currentFrameWidth = [_textFieldHour frame].size.width; + newFrame = CGRectMake(currentX, contentInset.top, currentFrameWidth, height); + currentX += currentFrameWidth; + + [_textFieldHour setFrame:newFrame]; + + currentFrameWidth = [_textFieldSeparatorThree frame].size.width; + currentX += timeSeparatorContentInset.left; + newFrame = CGRectMake(currentX, contentInset.top, currentFrameWidth, height); + currentX += currentFrameWidth; + + [_textFieldSeparatorThree setFrame:newFrame]; + + currentFrameWidth = [_textFieldMinute frame].size.width; + currentX += timeSeparatorContentInset.right; + newFrame = CGRectMake(currentX, contentInset.top, currentFrameWidth, height); + currentX += currentFrameWidth; + + [_textFieldMinute setFrame:newFrame]; + + if (![_textFieldSecond isHidden]) + { + currentFrameWidth = [_textFieldSeparatorFour frame].size.width; + currentX += timeSeparatorContentInset.left; + newFrame = CGRectMake(currentX, contentInset.top, currentFrameWidth, height); + currentX += currentFrameWidth; + + [_textFieldSeparatorFour setFrame:newFrame]; + + currentFrameWidth = [_textFieldSecond frame].size.width; + currentX += timeSeparatorContentInset.right; + newFrame = CGRectMake(currentX, contentInset.top, currentFrameWidth, height); + currentX += currentFrameWidth; + + [_textFieldSecond setFrame:newFrame]; + } + + if (![_textFieldPMAM isHidden]) + { + currentFrameWidth = [_textFieldPMAM frame].size.width; + currentX += [_datePicker currentValueForThemeAttribute:@"hour-ampm-margin"]; + newFrame = CGRectMake(currentX, contentInset.top, currentFrameWidth, height); + + [_textFieldPMAM setFrame:newFrame]; + } + } +} + +// Old behavior of the "content-inset" theme parameter +- (void)_deprecatedUpdatePositions +{ + var contentInset = [_datePicker valueForThemeAttribute:@"content-inset" inState:[_datePicker themeState]] || CGInsetMakeZero(), + separatorContentInset = [_datePicker valueForThemeAttribute:@"separator-content-inset"], + timeSeparatorContentInset = [_datePicker valueForThemeAttribute:@"time-separator-content-inset"] || separatorContentInset, + horizontalInset = contentInset.left - contentInset.right, + verticalInset = contentInset.top - contentInset.bottom, + firstTextField = _textFieldMonth, + secondTextField = _textFieldDay, + isAmericanFormat = [_datePicker _isAmericanFormat]; + + if (!isAmericanFormat) + { + firstTextField = _textFieldDay; + secondTextField = _textFieldMonth; + } + + [firstTextField setFrameOrigin:CGPointMake(horizontalInset,verticalInset)]; + + [_textFieldSeparatorOne setFrameOrigin:CGPointMake(CGRectGetMaxX([firstTextField frame]) + separatorContentInset.left, verticalInset)]; + + if ([firstTextField isHidden]) + [secondTextField setFrameOrigin:CGPointMake(horizontalInset,verticalInset)]; + else + [secondTextField setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldSeparatorOne frame]) + separatorContentInset.right, verticalInset)]; + + if (isAmericanFormat && [secondTextField isHidden]) + [_textFieldSeparatorTwo setFrameOrigin:CGPointMake(CGRectGetMaxX([firstTextField frame]) + separatorContentInset.left, verticalInset)]; + else + [_textFieldSeparatorTwo setFrameOrigin:CGPointMake(CGRectGetMaxX([secondTextField frame]) + separatorContentInset.left, verticalInset)]; + + [_textFieldYear setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldSeparatorTwo frame]) + separatorContentInset.right, verticalInset)]; + + if ([_textFieldMonth isHidden]) + [_textFieldHour setFrameOrigin:CGPointMake(horizontalInset, verticalInset)]; + else + [_textFieldHour setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldYear frame]) + [_datePicker currentValueForThemeAttribute:@"date-hour-margin"], verticalInset)]; + + [_textFieldSeparatorThree setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldHour frame]) + separatorContentInset.left, verticalInset)]; + [_textFieldMinute setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldSeparatorThree frame]) + separatorContentInset.right, verticalInset)]; + [_textFieldSeparatorFour setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldMinute frame]) + separatorContentInset.left, verticalInset)]; + [_textFieldSecond setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldSeparatorFour frame]) + separatorContentInset.right, verticalInset)]; + + if ([_textFieldSecond isHidden]) + [_textFieldPMAM setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldMinute frame]) + 2, verticalInset)]; + else + [_textFieldPMAM setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldSecond frame]) + 2, verticalInset)]; +} + +/*! Size to fit all of the textFields +*/ +- (void)_sizeToFit +{ + [_textFieldDay sizeToFit]; + [_textFieldMonth sizeToFit]; + [_textFieldYear sizeToFit]; + [_textFieldHour sizeToFit]; + [_textFieldMinute sizeToFit]; + [_textFieldSecond sizeToFit]; + [_textFieldSeparatorOne sizeToFit]; + [_textFieldSeparatorTwo sizeToFit]; + [_textFieldSeparatorThree sizeToFit]; + [_textFieldSeparatorFour sizeToFit]; + [_textFieldPMAM sizeToFit]; +} + +/*! Size to fit all of the textFields +*/ +- (void)_setControlSizes +{ + var controlSize = [_datePicker controlSize]; + + [_textFieldDay setControlSize:controlSize]; + [_textFieldMonth setControlSize:controlSize]; + [_textFieldYear setControlSize:controlSize]; + [_textFieldHour setControlSize:controlSize]; + [_textFieldMinute setControlSize:controlSize]; + [_textFieldSecond setControlSize:controlSize]; + [_textFieldSeparatorOne setControlSize:controlSize]; + [_textFieldSeparatorTwo setControlSize:controlSize]; + [_textFieldSeparatorThree setControlSize:controlSize]; + [_textFieldSeparatorFour setControlSize:controlSize]; + [_textFieldPMAM setControlSize:controlSize]; +} + + +#pragma mark - +#pragma mark Responder methods + +- (void)_updateResponderTextField +{ + [self _updateFirstLastTextField]; + [self _updateKeyView]; +} + +/*! Update the var _firstTextField and _lastTextField +*/ +- (void)_updateFirstLastTextField +{ + var datePickerElements = [_datePicker datePickerElements]; + + if ([_datePicker _isAmericanFormat]) + { + if (datePickerElements & CPYearMonthDayDatePickerElementFlag || datePickerElements & CPYearMonthDatePickerElementFlag) + [[self superview] setFirstTextField:_textFieldMonth]; + else + [[self superview] setFirstTextField:_textFieldHour]; + + if (datePickerElements & CPHourMinuteSecondDatePickerElementFlag || datePickerElements & CPHourMinuteDatePickerElementFlag) + [[self superview] setLastTextField:_textFieldPMAM]; + else + [[self superview] setLastTextField:_textFieldYear]; + } + else + { + if ((datePickerElements & CPYearMonthDayDatePickerElementFlag) == CPYearMonthDayDatePickerElementFlag) + [[self superview] setFirstTextField:_textFieldDay]; + else if (datePickerElements & CPYearMonthDayDatePickerElementFlag || datePickerElements & CPYearMonthDatePickerElementFlag) + [[self superview] setFirstTextField:_textFieldMonth]; + else + [[self superview] setFirstTextField:_textFieldHour]; + + if ((datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag) + [[self superview] setLastTextField:_textFieldSecond]; + else if (datePickerElements & CPHourMinuteSecondDatePickerElementFlag || datePickerElements & CPHourMinuteDatePickerElementFlag) + [[self superview] setLastTextField:_textFieldMinute]; + else + [[self superview] setLastTextField:_textFieldYear]; + } +} + +/*! Update the nextTextField params of all of the textField. This is used to move the current textField with the arrows +*/ +- (void)_updateKeyView +{ + [self _updateNextTextField]; + [self _updatePreviousTextField] +} + +- (void)_updateNextTextField +{ + var datePickerElements = [_datePicker datePickerElements], + firstTexField = _textFieldMonth, + secondTextField = _textFieldDay, + isAmericanFormat = [_datePicker _isAmericanFormat]; + + if (!isAmericanFormat) + { + firstTexField = _textFieldDay; + secondTextField = _textFieldMonth; + } + + if ((datePickerElements & CPYearMonthDayDatePickerElementFlag) == CPYearMonthDayDatePickerElementFlag) + [firstTexField setNextTextField:secondTextField]; + else + [firstTexField setNextTextField:_textFieldYear]; + + [secondTextField setNextTextField:_textFieldYear]; + + if (datePickerElements & CPHourMinuteSecondDatePickerElementFlag || datePickerElements & CPHourMinuteDatePickerElementFlag) + [_textFieldYear setNextTextField:_textFieldHour]; + else if (isAmericanFormat || (datePickerElements & CPYearMonthDayDatePickerElementFlag) == CPYearMonthDayDatePickerElementFlag) + [_textFieldYear setNextTextField:firstTexField]; + else + [_textFieldYear setNextTextField:secondTextField]; + + [_textFieldHour setNextTextField:_textFieldMinute]; + + if ((datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag) + [_textFieldMinute setNextTextField:_textFieldSecond]; + else if (isAmericanFormat) + [_textFieldMinute setNextTextField:_textFieldPMAM]; + else if ((datePickerElements & CPYearMonthDayDatePickerElementFlag) == CPYearMonthDayDatePickerElementFlag) + [_textFieldMinute setNextTextField:firstTexField]; + else if (datePickerElements & CPYearMonthDatePickerElementFlag) + [_textFieldMinute setNextTextField:secondTextField]; + else + [_textFieldMinute setNextTextField:_textFieldHour]; + + if (isAmericanFormat) + [_textFieldSecond setNextTextField:_textFieldPMAM]; + else if ((datePickerElements & CPYearMonthDayDatePickerElementFlag) == CPYearMonthDayDatePickerElementFlag) + [_textFieldSecond setNextTextField:firstTexField]; + else if (datePickerElements & CPYearMonthDatePickerElementFlag) + [_textFieldSecond setNextTextField:secondTextField]; + else + [_textFieldSecond setNextTextField:_textFieldHour]; + + if (datePickerElements & CPYearMonthDayDatePickerElementFlag) + [_textFieldPMAM setNextTextField:_textFieldMonth]; + else + [_textFieldPMAM setNextTextField:_textFieldHour]; +} + +- (void)_updatePreviousTextField +{ + var datePickerElements = [_datePicker datePickerElements], + firstTexField = _textFieldMonth, + secondTextField = _textFieldDay, + isAmericanFormat = [_datePicker _isAmericanFormat]; + + if (!isAmericanFormat) + { + firstTexField = _textFieldDay; + secondTextField = _textFieldMonth; + } + + if ((datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag) + [_textFieldPMAM setPreviousTextField:_textFieldSecond]; + else if (datePickerElements & CPHourMinuteDatePickerElementFlag) + [_textFieldPMAM setPreviousTextField:_textFieldMinute]; + + [_textFieldSecond setPreviousTextField:_textFieldMinute]; + [_textFieldMinute setPreviousTextField:_textFieldHour]; + + if (datePickerElements & CPYearMonthDatePickerElementFlag) + [_textFieldHour setPreviousTextField:_textFieldYear]; + else if (isAmericanFormat) + [_textFieldHour setPreviousTextField:_textFieldPMAM]; + else if ((datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag) + [_textFieldHour setPreviousTextField:_textFieldSecond]; + else + [_textFieldHour setPreviousTextField:_textFieldMinute]; + + if (!isAmericanFormat) + [_textFieldYear setPreviousTextField:_textFieldMonth]; + else if ((datePickerElements & CPYearMonthDayDatePickerElementFlag) == CPYearMonthDayDatePickerElementFlag) + [_textFieldYear setPreviousTextField:_textFieldDay]; + else + [_textFieldYear setPreviousTextField:_textFieldMonth]; + + [secondTextField setPreviousTextField:firstTexField]; + + if (isAmericanFormat && datePickerElements & CPHourMinuteDatePickerElementFlag) + [firstTexField setPreviousTextField:_textFieldPMAM]; + else if ((datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag) + [firstTexField setPreviousTextField:_textFieldSecond]; + else if (datePickerElements & CPHourMinuteDatePickerElementFlag) + [firstTexField setPreviousTextField:_textFieldMinute]; + else + [firstTexField setPreviousTextField:_textFieldYear]; +} + +@end diff --git a/AppKit/CPDatePicker/_CPDatePickerHeaderView.j b/AppKit/CPDatePicker/_CPDatePickerHeaderView.j new file mode 100644 index 000000000..816120e1f --- /dev/null +++ b/AppKit/CPDatePicker/_CPDatePickerHeaderView.j @@ -0,0 +1,306 @@ +/* _CPDatePickerHeaderView.j +* AppKit +* +* Created by Alexandre Wilhelm +* Copyright 2012 +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +@import + +@import "CPControl.j" +@import "CPTextField.j" +@import "CPButton.j" + +@class CPDatePicker + +var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su"], + CPShortWeekDayNameArrayUS = [@"Su", @"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa"], + CPShortWeekDayNameArrayFr = [@"L", @"M", @"M", @"J", @"V", @"S", @"D"], + CPShortWeekDayNameArrayDe = [@"M", @"D", @"M", @"D", @"F", @"S", @"S"], + CPShortWeekDayNameArrayEs = [@"L", @"M", @"X", @"J", @"V", @"S", @"D"], + CPShortMonthNameArrayEn = [@"Jan", @"Feb", @"Mar", @"Apr", @"May", @"Jun", @"Jul", @"Aug", @"Sep", @"Oct", @"Nov", @"Dec"], + CPShortMonthNameArrayFr = [@"janv.", String.fromCharCode(102, 233, 118, 46), @"mars", @"apr.", @"mai", @"juin", @"juil.", String.fromCharCode(97, 111, 251, 116), @"sept.", @"oct.", @"nov.", String.fromCharCode(100, 233, 99, 46)], + CPShortMonthNameArrayDe = [@"Jan", @"Feb", String.fromCharCode(77, 228, 114), @"Apr", @"Mai", @"Jun", @"Jul", @"Aug", @"Sep", @"Okt", @"Nov", @"Dez"], + CPShortMonthNameArrayEs = [@"ene", @"feb", @"mar", @"abr", @"may", @"jun", @"jul", @"ago", @"sep", @"oct", @"nov", @"dic"]; + +@implementation _CPDatePickerHeaderView : CPControl +{ + CPArray _dayLabels; + CPArray _monthNames; + CPButton _nextButton; + CPButton _previousButton; + CPButton _currentButton; + CPDatePicker _datePicker; + CPDate _date; + CPTextField _title; +} + + +#pragma mark Init methods + +/*! Init a new instance of _CPDatePickerHeaderView + @param aFrame + @param aDatePicker + @return a new instance of _CPDatePickerHeaderView +*/ +- (id)initWithFrame:(CGRect)aFrame datePicker:(CPDatePicker)aDatePicker delegate:(id)aDelegate +{ + self = [super initWithFrame:aFrame]; + + if (self) + { + _datePicker = aDatePicker; + + // Title + _title = [[CPTextField alloc] initWithFrame:CGRectMakeZero()]; + + [_title setValue:[_datePicker valueForThemeAttribute:@"title-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal]; + [_title setValue:[_datePicker valueForThemeAttribute:@"title-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; + [_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; + [_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; + + [_title setValue:[_datePicker valueForThemeAttribute:@"title-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled]; + [_title setValue:[_datePicker valueForThemeAttribute:@"title-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; + [_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; + [_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; + + [self addSubview:_title]; + + _dayLabels = [CPArray array]; + + // Days + for (var i = 0, count = [[self _dayNames] count]; i < count; i++) + { + var label = [[CPTextField alloc] initWithFrame:CGRectMakeZero()]; + [label setValue:[_datePicker valueForThemeAttribute:@"tile-text-alignment"] forThemeAttribute:@"alignment"]; + + var contentInset = [_datePicker valueForThemeAttribute:@"tile-content-inset"]; + + if (contentInset) + [label setValue:contentInset forThemeAttribute:@"content-inset"]; + + [label setValue:[_datePicker valueForThemeAttribute:@"weekday-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal]; + [label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; + [label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; + [label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; + + [label setValue:[_datePicker valueForThemeAttribute:@"weekday-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled]; + [label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; + [label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; + [label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; + + [_dayLabels addObject:label]; + [self addSubview:label]; + } + + // Arrows + var size = [_datePicker valueForThemeAttribute:@"previous-button-size"]; + + _previousButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)]; + [_previousButton setButtonType:CPMomentaryChangeButton]; + [_previousButton setBordered:NO]; + [_previousButton setImage:[_datePicker valueForThemeAttribute:@"arrow-image-left"]]; + [_previousButton setAlternateImage:[_datePicker valueForThemeAttribute:@"arrow-image-left-highlighted"]]; + [self addSubview:_previousButton]; + + size = [_datePicker valueForThemeAttribute:@"next-button-size"]; + + _nextButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)]; + [_nextButton setButtonType:CPMomentaryChangeButton]; + [_nextButton setBordered:NO]; + [_nextButton setImage:[_datePicker valueForThemeAttribute:@"arrow-image-right"]]; + [_nextButton setAlternateImage:[_datePicker valueForThemeAttribute:@"arrow-image-right-highlighted"]]; + [self addSubview:_nextButton]; + + size = [_datePicker valueForThemeAttribute:@"current-button-size"]; + + _currentButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)]; + [_currentButton setButtonType:CPMomentaryChangeButton]; + [_currentButton setBordered:NO]; + [_currentButton setImage:[_datePicker valueForThemeAttribute:@"circle-image"]]; + [_currentButton setAlternateImage:[_datePicker valueForThemeAttribute:@"circle-image-highlighted"]]; + [self addSubview:_currentButton]; + + [_previousButton setTarget:aDelegate]; + [_previousButton setAction:@selector(_clickArrowPrevious:)]; + [_previousButton setContinuous:YES]; + + [_nextButton setTarget:aDelegate]; + [_nextButton setAction:@selector(_clickArrowNext:)]; + [_nextButton setContinuous:YES]; + + [_currentButton setTarget:aDelegate]; + [_currentButton setAction:@selector(_currentMonth:)]; + + [self setNeedsLayout]; + } + + return self; +} + + +#pragma mark - +#pragma mark Getter Setter methods + +/*! Return the day names depending on the CPLocale of the datePicker + @return an array +*/ +- (CPArray)_dayNames +{ + switch ([[_datePicker locale] objectForKey:CPLocaleLanguageCode]) + { + case @"en": + + // Check if it's in the american format. If yes the week will begin the sunday + if ([_datePicker _isAmericanFormat]) + return CPShortWeekDayNameArrayUS; + else + return CPShortWeekDayNameArrayEn; + break; + + case @"es": + return CPShortWeekDayNameArrayEs; + break; + + case @"de": + return CPShortWeekDayNameArrayDe; + break; + + case @"fr": + return CPShortWeekDayNameArrayFr; + break; + + default: + return CPShortWeekDayNameArrayEn; + break; + } +} + +/*! Return the month names depending on the CPLocale of the datePicker + @return an array +*/ +- (CPArray)_monthNames +{ + switch ([[_datePicker locale] objectForKey:CPLocaleLanguageCode]) + { + case @"en": + return CPShortMonthNameArrayEn; + break; + + case @"es": + return CPShortMonthNameArrayEs; + break; + + case @"de": + return CPShortMonthNameArrayDe; + break; + + case @"fr": + return CPShortMonthNameArrayFr; + break; + + default: + return CPShortMonthNameArrayEn; + break; + } + +} + +/*! Set the monthDate of the header + @aMonthDate the new monthDate +*/ +- (void)setMonthForDate:(CPDate)aMonthDate +{ + _date = aMonthDate; + [self setNeedsLayout]; +} + +/*! Set enabled + @param aBoolean +*/ +- (void)setEnabled:(BOOL)aBoolean +{ + [_previousButton setEnabled:aBoolean]; + [_nextButton setEnabled:aBoolean]; + [_currentButton setEnabled:aBoolean]; + [_dayLabels makeObjectsPerformSelector:@selector(setEnabled:) withObject:aBoolean]; + [_title setEnabled:aBoolean]; +} + + +#pragma mark - +#pragma mark Layout methods + +/*! Layout the subviews +*/ +- (void)layoutSubviews +{ + var bounds = [self bounds], + dayNames = [self _dayNames], + width = CGRectGetWidth(bounds), + buttonInset = [_datePicker valueForThemeAttribute:@"arrow-inset"], + numberOfLabels = [_dayLabels count], + labelWidth = width / numberOfLabels, + sizeButtonLeft = [[_datePicker valueForThemeAttribute:@"arrow-image-left"] size], + sizeButtonRight = [[_datePicker valueForThemeAttribute:@"arrow-image-right"] size], + sizeButtonCircle = [[_datePicker valueForThemeAttribute:@"circle-image"] size], + sizeTileWidth = [_datePicker valueForThemeAttribute:@"size-tile"].width, + titleInset = [_datePicker valueForThemeAttribute:@"title-inset"], + dayLabelInset = [_datePicker valueForThemeAttribute:@"day-label-inset"]; + + // Arrows + [_nextButton setFrame:CGRectMake(width - [_nextButton frameSize].width - buttonInset.right, buttonInset.top, sizeButtonRight.width, sizeButtonRight.height)]; + [_currentButton setFrame:CGRectMake(CGRectGetMinX([_nextButton frame]) - sizeButtonCircle.width - buttonInset.left - buttonInset.right, buttonInset.top, sizeButtonCircle.width, sizeButtonCircle.height)]; + [_previousButton setFrame:CGRectMake(CGRectGetMinX([_currentButton frame]) - sizeButtonLeft.width - buttonInset.left - buttonInset.right, buttonInset.top, sizeButtonLeft.width, sizeButtonLeft.height)]; + + var firstDayTileX; + + // Weekday label + for (var i = 0; i < numberOfLabels; i++) + { + var dayLabel = _dayLabels[i]; + + [dayLabel setStringValue:dayNames[i]]; + + if (dayLabelInset) // Beginning with Aristo3 + { + var thisWidth = ROUND((i+1) * sizeTileWidth) - ROUND(i * sizeTileWidth); + + [dayLabel sizeToFit]; + [dayLabel setFrame:CGRectMake(dayLabelInset.left + ROUND(i * sizeTileWidth), dayLabelInset.top, thisWidth, [dayLabel frameSize].height)]; + } + else + { + [dayLabel sizeToFit]; + [dayLabel setFrameOrigin:CGPointMake(sizeTileWidth * (i + 1) - sizeTileWidth / 2 - [dayLabel frameSize].width / 2, 23)]; + + if (i == 0) + firstDayTileX = sizeTileWidth * (i + 1) - sizeTileWidth / 2 - [dayLabel frameSize].width / 2; + } + } + + // Title + [_title setStringValue:[CPString stringWithFormat:@"%s %i", [self _monthNames][_date.getMonth()], _date.getFullYear()]]; + [_title sizeToFit]; + + if (titleInset) // Beginning with Aristo3 + [_title setFrameOrigin:CGPointMake(titleInset.left, titleInset.top)]; + else + [_title setFrameOrigin:CGPointMake(firstDayTileX, 6)]; +} + +@end diff --git a/AppKit/CPDatePicker/_CPDatePickerMonthView.j b/AppKit/CPDatePicker/_CPDatePickerMonthView.j new file mode 100644 index 000000000..9a09b06f9 --- /dev/null +++ b/AppKit/CPDatePicker/_CPDatePickerMonthView.j @@ -0,0 +1,636 @@ +/* _CPDatePickerMonthView.j +* AppKit +* +* Created by Alexandre Wilhelm +* Copyright 2012 +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +@import + +@import "CPControl.j" +@import "_CPDatePickerDayView.j" + +@class CPDatePicker + +@global CPSingleDateMode +@global CPRangeDateMode + +@implementation _CPDatePickerMonthView : CPControl +{ + BOOL _isMonthJustChanged; + CPArray _dayTiles; + CPDate _clickDate; + CPDate _dragDate; + CPDate _date; + CPDate _previousMonth @accessors(property=previousMonth); + CPDate _nextMonth @accessors(property=nextMonth); + CPDatePicker _datePicker; + CPEvent _eventDragged; + CPTimer _timerMonth; + id _delegate; + int _indexDayTile; +} + + +#pragma mark Init methods + +/*! Init a _CPDatePickerMonthView + @param aFrame + @param aDatePicker + @return a new _CPDatePickerMonthView +*/ +- (id)initWithFrame:(CGRect)aFrame datePicker:(CPDatePicker)aDatePicker delegate:(id)aDelegate +{ + if (self = [super initWithFrame:aFrame]) + { + _delegate = aDelegate; + _isMonthJustChanged = NO; + _indexDayTile = -1; + _datePicker = aDatePicker; + _dayTiles = [CPArray array]; + + // Create tiles + for (var i = 0; i < 42; i++) + { + var dayView = [[_CPDatePickerDayView alloc] initWithFrame:CGRectMakeZero() withDatePicker:_datePicker]; + [self addSubview:dayView]; + [_dayTiles addObject:dayView]; + } + + [self setNeedsLayout]; + } + return self; +} + + +#pragma mark - +#pragma mark Getter Setter methods + +/*! Set the monthDate of the component + @param aDate +*/ +- (void)setMonthForDate:(CPDate)aDate +{ + if (_dragDate) + { + if (_dragDate.getMonth() != _date.getMonth()) + _isMonthJustChanged = YES; + + _date = [_dragDate copy]; + } + else + { + _date = [aDate copy]; + } + + if (![aDate isEqualToDate:[CPDate distantFuture]]) + { + // Reset the date to the first day of the month & midnight + _date.setDate(1); + [_date _resetToMidnight]; + + // There must be a better way to do this. + var firstDay = [_date copy]; + firstDay.setDate(1); + + // Set the previous and next month date. This is usefull for the tile of the next/previous month + _previousMonth = new Date(firstDay.getTime() - 86400000); + _previousMonth.setDate(1); + + _nextMonth = new Date(firstDay.getTime() + (([_date _daysInMonth] + 1) * 86400000)); + _nextMonth.setDate(1); + } + + [self reloadData]; + + if (_isMonthJustChanged) + { + var dayTile = [_dayTiles objectAtIndex:_indexDayTile]; + + if ([dayTile date].getMonth() == _date.getMonth()) + { + [self mouseDragged:_eventDragged]; + } + else + { + if ([dayTile date].getMonth() - _date.getMonth() == 1 || [dayTile date].getFullYear() - _date.getFullYear() == 1) + _timerMonth = [CPTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(_timerNextMonthEvent:) userInfo:nil repeats:NO]; + else + _timerMonth = [CPTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(_timerPreviousMonthEvent:) userInfo:nil repeats:NO]; + } + } +} + +- (void)monthDate +{ + return _date; +} + +/*! Return the size of a tile +*/ +- (CGSize)tileSize +{ + return [_datePicker valueForThemeAttribute:@"size-tile"]; +} + +/*! Return the first index day of the month +*/ +- (int)startOfWeekForDate:(CPDate)aDate +{ + var day = aDate.getDay(); + + // American people begins the week the sunday + if (![_datePicker _isAmericanFormat]) + return (day + 6) % 7; + + return day; +} + +/*! Set enabled + @param aBoolean +*/ +- (void)setEnabled:(BOOL)aBoolean +{ + [super setEnabled:aBoolean]; + [self reloadData]; +} + + +/*! Return the index of tile depending of the giving event + @param anEvent + @return an index +*/ +- (CPInteger)indexOfTileForEvent:(CPEvent)anEvent +{ + var locationInView = [self convertPoint:[anEvent locationInWindow] fromView:nil], + tileSize = [self tileSize], + borderWidth = [_datePicker valueForThemeAttribute:@"border-width"], + margin = [_datePicker valueForThemeAttribute:@"tile-margin"] || CGSizeMakeZero(), + tileInset = [_datePicker valueForThemeAttribute:@"tile-inset"] || CGInsetMakeZero(); + + // Get the week row + var rowIndex = FLOOR((locationInView.y - tileInset.top - margin.height) / (tileSize.height + 2 * margin.height + borderWidth)), + columnIndex = FLOOR((locationInView.x - tileInset.left) / (tileSize.width + 2 * margin.width + borderWidth)); + + columnIndex = MIN(MAX(columnIndex, 0), 6); + rowIndex = MIN(MAX(rowIndex, 0), 5); + + var tileIndex = (rowIndex * 7) + columnIndex; + + return tileIndex; +} + +#pragma mark - +#pragma mark Reload data + +/*! Reload the data +*/ +- (void)reloadData +{ + if (!_date) + return; + + var currentMonth = _date, + startOfMonthDay = [self startOfWeekForDate:currentMonth], + daysInPreviousMonth = [_previousMonth _daysInMonth], + firstDayToShowInPreviousMonth = daysInPreviousMonth - startOfMonthDay, + currentDate = new Date(_previousMonth.getFullYear(), _previousMonth.getMonth(), firstDayToShowInPreviousMonth), + now = [CPDate date], + dateValue = [_datePicker dateValue]; + + // Update the tiles + for (var i = 0; i < [_dayTiles count]; i++) + { + var dayTile = _dayTiles[i]; + + // Increment to next day + currentDate.setTime(currentDate.getTime() + 90000000); + [currentDate _resetToMidnight]; + + var isPresentMonth = (now.getMonth() == currentDate.getMonth() + && now.getFullYear() == currentDate.getFullYear()); + + [dayTile setDate:[currentDate copy]]; + [dayTile setStringValue:currentDate.getDate()]; + [dayTile setDisabled:/*![self isEnabled] ||*/ currentDate.getMonth() !== currentMonth.getMonth() || currentDate < [_datePicker minDate] || currentDate > [_datePicker maxDate]]; + [dayTile setHighlighted:isPresentMonth && currentDate.getDate() == now.getDate()]; + } + + // Select the dates + [self _selectDate:[_datePicker dateValue] timeInterval:[_datePicker timeInterval]]; +} + + +#pragma mark - +#pragma mark Select methods + +/*! Select one date or several date depending of the giving interval + @param aStartDate + @param anInterval; +*/ +- (void)_selectDate:(CPDate)aStartDate timeInterval:(CPInteger)anInterval +{ + var endDate = [[CPDate alloc] initWithTimeInterval:anInterval sinceDate:aStartDate], + tilesCount = [_dayTiles count]; + + aStartDate = [aStartDate copy]; + + [aStartDate _resetToMidnight]; + [endDate _resetToMidnight]; + + var firstSelected = NO; + + for (var i = 0; i < tilesCount; i++) + { + var tile = _dayTiles[i], + tileDate = [[tile date] copy], + selected = NO; + + [tileDate _resetToMidnight]; + + if (aStartDate) + selected = tileDate >= aStartDate && tileDate <= endDate; + + // Select a tile + [tile setSelected:selected]; + + // If we are disabled, we have to disable selected tiles so they will appear disabled + [tile setDisabled:[tile isDisabled] || (selected && ![self isEnabled])]; + + if (selected) + { + if (!firstSelected) + { + firstSelected = YES; + [tile setFirstSelected:YES]; + } + else + [tile setFirstSelected:NO]; + + [tile setLastSelected:NO]; + } + else + { + if (firstSelected) + { + firstSelected = NO; + + // As there was a first selected and we are now on an unselected tile, + // we are sure that i > 0 + [_dayTiles[i-1] setLastSelected:YES]; + } + } + } +} + +#pragma mark - +#pragma mark Layout methods + +/*! Tile the view +*/ +- (void)tile +{ + var tileSize = [self tileSize], + width = tileSize.width, + height = tileSize.height, + tilesCount = [_dayTiles count], + borderWidth = [_datePicker valueForThemeAttribute:@"border-width"], + margin = [_datePicker valueForThemeAttribute:@"tile-margin"], + tileInset = [_datePicker valueForThemeAttribute:@"tile-inset"], + thisWidth, + thisX, + dayInWeek, + weekInMonth, + tileFrame, + tileIndex; + + // Set the frame of the tiles + for (tileIndex = 0; tileIndex < tilesCount; tileIndex++) + { + dayInWeek = tileIndex % 7; + weekInMonth = (tileIndex - dayInWeek) / 7; + tileFrame; + + if (margin) // Beginning with Aristo3 + { + thisX = ROUND(dayInWeek * (width + 2 * margin.width)); + thisWidth = ROUND((dayInWeek+1) * (width + 2 * margin.width)) - thisX; + + tileFrame = CGRectMake(tileInset.left + thisX, tileInset.top + margin.height + weekInMonth * (height + 2 * margin.height), thisWidth + borderWidth, height + borderWidth); + } + else + tileFrame = CGRectMake(dayInWeek * width, weekInMonth * height, width + borderWidth, height + borderWidth); + + [_dayTiles[tileIndex] setFrame:tileFrame]; + [_dayTiles[tileIndex] setDayInWeek:dayInWeek]; + } + + [self reloadData]; +} + +/*! Layout the subviews +*/ +- (void)layoutSubviews +{ + [super layoutSubviews]; + + [self tile]; + [_dayTiles makeObjectsPerformSelector:@selector(setNeedsLayout)]; +} + +/*! Draw the component. This draws the border of the tile. + The selected tile are drawed in the drawRect method of the tile. But the unselected tile here. + It avoids some problems with tiles over other tiles (otherwise the color of the tile border would be different). + Rememeber that the first pixel of a tile are over the last pixel of the last tile (because the border) +*/ +- (void)drawRect:(CGRect)aRect +{ + [super drawRect:aRect]; + + if ([_datePicker isCSSBased]) + { + // We just have to draw the separator (if any) + // No separator color means no separator + var separatorColor = [_datePicker valueForThemeAttribute:@"separator-color"], + separatorHeight = [_datePicker valueForThemeAttribute:@"separator-height"], + separatorMarginWidth = [_datePicker valueForThemeAttribute:@"separator-margin-width"]; + + if (separatorColor) + { + var context = [[CPGraphicsContext currentContext] graphicsPort], + bounds = [self bounds]; + + CGContextBeginPath(context); + CGContextSetStrokeColor(context, separatorColor); + CGContextSetLineWidth(context, separatorHeight); + CGContextMoveToPoint(context, separatorMarginWidth, 0.5); + CGContextAddLineToPoint(context, bounds.size.width - separatorMarginWidth, 0.5); + CGContextStrokePath(context); + CGContextClosePath(context); + } + + return; + } + + var context = [[CPGraphicsContext currentContext] graphicsPort], + width = [self tileSize].width, + height = [self tileSize].height, + isBorderPair = ([_datePicker valueForThemeAttribute:@"border-width"] % 2) == 0; + + CGContextBeginPath(context); + CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:[_datePicker themeState]]); + CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]); + + if ([_datePicker isBordered]) + { + for (var i = 0; i < 6; i++) + { + var y = i * height; + + // Very usefull to avoid to have a line of two pixels instead one + if (!isBorderPair) + y += 0.5; + + CGContextMoveToPoint(context, 0, y); + CGContextAddLineToPoint(context, [self bounds].size.width, y); + } + + for (var i = 0; i < 7; i++) + { + var x = i * width; + + // Very usefull to avoid to have a line of two pixels instead one + if (!isBorderPair) + x += 0.5; + + CGContextMoveToPoint(context, x, 0); + CGContextAddLineToPoint(context, x, [self bounds].size.height); + } + } + else + { + var y = 0; + + // Very usefull to avoid to have a line of two pixels instead one + if (!isBorderPair) + y += 0.5; + + CGContextMoveToPoint(context, 0, y); + CGContextAddLineToPoint(context, [self bounds].size.width, y); + } + + CGContextStrokePath(context); + CGContextClosePath(context); +} + + +#pragma mark - +#pragma mark Mouse event + +/*! Mouse down event +*/ +- (void)mouseDown:(CPEvent)anEvent +{ + if (![self isEnabled]) + return; + + var dayTile = [_dayTiles objectAtIndex:[self indexOfTileForEvent:anEvent]], + dateTile = [[dayTile date] copy], + dateValue = [_datePicker dateValue]; + + _clickDate = [dateTile copy]; + _dragDate = nil; + _indexDayTile = -1; + _eventDragged = nil; + +#if PLATFORM(DOM) + _datePicker._invokedByUserEvent = YES; +#endif + + // Check if we have to change or not the month of the component + if ([dayTile date].getMonth() == _date.getMonth()) + { + if ([_datePicker datePickerMode] == CPRangeDateMode && [anEvent modifierFlags] & CPShiftKeyMask) + { + var dateValueAtMidnight = [[_datePicker dateValue] copy]; + + [dateValueAtMidnight _resetToMidnight]; + + if (dateTile < dateValueAtMidnight) + { + var interval; + + if (dateTile == dateValueAtMidnight) + interval = [_datePicker timeInterval]; + else + interval = ([dateValueAtMidnight timeIntervalSinceDate:dateTile] + [_datePicker timeInterval]); + + [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:interval]; + } + else if ([[dayTile date] isEqualToDate:dateValueAtMidnight]) + { + [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0]; + } + else + { + [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:[dateValueAtMidnight copy]] timeInterval:([dateTile timeIntervalSinceDate:dateValueAtMidnight])]; + } + + // Be sure to display the good month + [_delegate setDateValue:dateTile]; + } + else + { + var minDate = [[_datePicker minDate] copy], + maxDate = [[_datePicker maxDate] copy]; + + [minDate _resetToMidnight]; + [maxDate _resetToLastSeconds]; + + if (dateTile >= minDate && dateTile <= maxDate) + [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0]; + } + } + else + { + // Check the year and the month. The year is usefull when changing from Jan to Dec. + if (_date.getMonth() - [dayTile date].getMonth() == 1 || _date.getFullYear() - [dayTile date].getFullYear() == 1) + [_delegate _displayPreviousMonth]; + else + [_delegate _displayNextMonth]; + } + +#if PLATFORM(DOM) + _datePicker._invokedByUserEvent = NO; +#endif +} + +/*! Mouse dragged event +*/ +- (void)mouseDragged:(CPEvent)anEvent +{ + if (![self isEnabled] || !CGRectContainsPoint([self bounds],[self convertPoint:[anEvent locationInWindow] fromView:nil])) + return; + + var dayTile = [_dayTiles objectAtIndex:[self indexOfTileForEvent:anEvent]], + dateTile = [[dayTile date] copy], + dateValue = [_datePicker dateValue]; + + _dragDate = [dateTile copy]; + _indexDayTile = [self indexOfTileForEvent:anEvent]; + _eventDragged = anEvent; + +#if PLATFORM(DOM) + _datePicker._invokedByUserEvent = YES; +#endif + + if ([_datePicker datePickerMode] == CPSingleDateMode) + { + // Check if we have to change or not the month of the component + if ([dayTile date].getMonth() == _date.getMonth()) + { + [_timerMonth invalidate]; + _isMonthJustChanged = NO; + + [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0]; + } + else if (!_isMonthJustChanged) + { + [_timerMonth invalidate]; + _isMonthJustChanged = NO; + + // Check the year and the month. The year is usefull when changing from Jan to Dec. + if (_date.getMonth() - [dayTile date].getMonth() == 1 || _date.getFullYear() - [dayTile date].getFullYear() == 1) + [_delegate _displayPreviousMonth]; + else + [_delegate _displayNextMonth]; + } + } + else + { + if (dateTile.getMonth() == _date.getMonth() || !_isMonthJustChanged) + { + [_timerMonth invalidate]; + _isMonthJustChanged = NO; + + var dateValueAtMidnight = [[_datePicker dateValue] copy]; + + [dateValueAtMidnight _resetToMidnight]; + + if (dateTile < _clickDate) + [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:[_clickDate timeIntervalSinceDate:dateTile]]; + else if ([[dayTile date] isEqualToDate:_clickDate]) + [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0]; + else + [_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:_clickDate] timeInterval:[dateTile timeIntervalSinceDate:dateValueAtMidnight]]; + } + } + +#if PLATFORM(DOM) + _datePicker._invokedByUserEvent = NO; +#endif +} + +- (void)mouseUp:(CPEvent)anEvent +{ + [_timerMonth invalidate]; + _dragDate = nil; + _clickDate = nil; + _isMonthJustChanged = NO; + _indexDayTile = -1; + _eventDragged = nil; +} + + +#pragma mark - +#pragma mark Timer + +- (void)_timerNextMonthEvent:(CPEvent)anEvent +{ + if (_isMonthJustChanged) + { + _dragDate.setMonth(_date.getMonth() + 1); + [_delegate _displayNextMonth]; + } +} + +- (void)_timerPreviousMonthEvent:(CPEvent)anEvent +{ + if (_isMonthJustChanged) + { + _dragDate.setMonth(_date.getMonth() - 1); + [_delegate _displayPreviousMonth]; + } +} + + +#pragma mark - +#pragma mark Date methods + +- (CPDate)_hoursMinutesSecondsFromDatePickerForDate:(CPDate)aDate +{ + var dateValue = [_datePicker dateValue]; + + aDate.setHours(dateValue.getHours()); + aDate.setMinutes(dateValue.getMinutes()); + aDate.setSeconds(dateValue.getSeconds()); + + return aDate; +} + +@end + diff --git a/AppKit/CPDatePicker/_CPDatePickerTextField.j b/AppKit/CPDatePicker/_CPDatePickerTextField.j index 987cafb6c..9f53e7848 100644 --- a/AppKit/CPDatePicker/_CPDatePickerTextField.j +++ b/AppKit/CPDatePicker/_CPDatePickerTextField.j @@ -31,16 +31,14 @@ @import @class CPDatePicker -@class _CPDatePickerElementTextField @class _CPDatePickerElementView -@class _CPDatePickerMonthView -@class _CPDatePickerHeaderView + +@import "_CPDatePickerElementView.j" @global CPSingleDateMode @global CPRangeDateMode @global CPTextFieldAndStepperDatePickerStyle -@global CPClockAndCalendarDatePickerStyle @global CPTextFieldDatePickerStyle @global CPHourMinuteDatePickerElementFlag @@ -50,9 +48,6 @@ @global CPYearMonthDayDatePickerElementFlag @global CPEraDatePickerElementFlag -var CPDatePickerElementTextFieldBecomeFirstResponder = @"CPDatePickerElementTextFieldBecomeFirstResponder", - CPDatePickerElementTextFieldAMPMChangedNotification = @"CPDatePickerElementTextFieldAMPMChangedNotification"; - var CPZeroKeyCode = 48, CPNineKeyCode = 57, CPMajAKeyCode = 65, @@ -70,10 +65,10 @@ var CPZeroKeyCode = 48, _CPDatePickerElementView _datePickerElementView; CPDatePicker _datePicker; CPStepper _stepper; + CPInteger _datePickerElements @accessors(getter=datePickerElements); } -#pragma mark - #pragma mark Init - (id)initWithFrame:(CGRect)aFrame withDatePicker:(CPDatePicker)aDatePicker @@ -88,6 +83,8 @@ var CPZeroKeyCode = 48, - (void)_init { + _datePickerElements = [_datePicker datePickerElements]; + _datePickerElementView = [[_CPDatePickerElementView alloc] initWithFrame:CGRectMakeZero() withDatePicker:_datePicker]; [self addSubview:_datePickerElementView]; @@ -98,6 +95,18 @@ var CPZeroKeyCode = 48, [self setNeedsLayout]; [self setNeedsDisplay:YES]; + +#if PLATFORM(DOM) + if ([_datePicker currentValueForThemeAttribute:@"uses-focus-ring"]) + { + // As with overflow:hidden, views are clipping their content, in order + // to show a focus ring (which is external to a view), we need to let + // content extend outside the view. + _datePicker._DOMElement.style.overflow = "visible"; + _DOMElement.style.overflow = "visible"; + _datePickerElementView._DOMElement.style.overflow = "visible"; + } +#endif } @@ -163,6 +172,28 @@ var CPZeroKeyCode = 48, [_datePickerElementView setEnabled:aBoolean]; } +- (void)setTextColor:(CPColor)aColor +{ + [super setTextColor:aColor]; + [_datePickerElementView setTextColor:aColor]; +} + +- (void)setTextFont:(CPFont)aFont +{ + [self setFont:aFont]; + [_datePickerElementView setTextFont:aFont]; +} + +- (void)setDatePickerElements:(CPInteger)aDatePickerElements +{ + if (_datePickerElements === aDatePickerElements) + return; + + _datePickerElements = aDatePickerElements; + + [self setNeedsDisplay:YES]; + [self setNeedsLayout]; +} #pragma mark - #pragma mark Notification methods @@ -402,12 +433,12 @@ var CPZeroKeyCode = 48, */ - (void)layoutSubviews { - if ([_datePicker datePickerStyle] == CPClockAndCalendarDatePickerStyle) - return; + [_datePicker _sizeToControlSize]; + [self setFrameSize:[_datePicker frameSize]]; [super layoutSubviews]; - var frameSize = CGSizeMakeZero(), + var frameSize, bezelInset = [_datePicker valueForThemeAttribute:@"bezel-inset" inState:[_datePicker themeState]]; // Check the mode to display or not the stepper @@ -423,6 +454,8 @@ var CPZeroKeyCode = 48, [_datePickerElementView setFrameSize:frameSize]; [_datePickerElementView setFrameOrigin:CGPointMake(bezelInset.left, bezelInset.top)]; + + [_stepper setFrameOrigin:CGPointMake(CGRectGetMaxX([_datePickerElementView frame]) + [_datePicker currentValueForThemeAttribute:@"stepper-margin"], bezelInset.top + CGRectGetHeight([_datePickerElementView frame]) / 2 - CGRectGetHeight([_stepper frame]) / 2)]; } else if ([_datePicker datePickerStyle] == CPTextFieldDatePickerStyle) { @@ -436,8 +469,7 @@ var CPZeroKeyCode = 48, [_stepper setHidden:YES]; } - [_stepper setFrameOrigin:CGPointMake(CGRectGetMaxX([_datePickerElementView frame]) + [_datePicker currentValueForThemeAttribute:@"stepper-margin"], bezelInset.top + CGRectGetHeight([_datePickerElementView frame]) / 2 - CGRectGetHeight([_stepper frame]) / 2)]; - + // FIXME: should be done in a self setControlSize [_datePickerElementView setControlSize:[_datePicker controlSize]]; [_datePickerElementView setNeedsLayout]; @@ -468,1417 +500,3 @@ var CPZeroKeyCode = 48, } @end - - -/*! This class is used to display the elements (all of the textFields) in the datePicker -*/ -@implementation _CPDatePickerElementView : CPControl -{ - _CPDatePickerElementTextField _textFieldDay; - _CPDatePickerElementTextField _textFieldMonth; - _CPDatePickerElementTextField _textFieldYear; - _CPDatePickerElementTextField _textFieldHour; - _CPDatePickerElementTextField _textFieldMinute; - _CPDatePickerElementTextField _textFieldSecond; - _CPDatePickerElementTextField _textFieldPMAM; - CPDatePicker _datePicker; - CPTextField _textFieldSeparatorOne; - CPTextField _textFieldSeparatorTwo; - CPTextField _textFieldSeparatorThree; - CPTextField _textFieldSeparatorFour; -} - - -#pragma mark - -#pragma mark Init - -- (id)initWithFrame:(CGRect)aFrame withDatePicker:(CPDatePicker)aDatePicker -{ - if (self = [super init]) - { - _datePicker = aDatePicker; - [self _init]; - } - return self; -} - -- (void)_init -{ - _textFieldDay = [_CPDatePickerElementTextField new]; - [_textFieldDay setBezeled:NO]; - [_textFieldDay setBordered:NO]; - [_textFieldDay setDateType:CPDayDateType]; - [_textFieldDay setDatePicker:_datePicker]; - [_textFieldDay setAlignment:CPRightTextAlignment]; - [_textFieldDay setDatePickerElementView:self]; - [self addSubview:_textFieldDay]; - - _textFieldMonth = [_CPDatePickerElementTextField new]; - [_textFieldMonth setBezeled:NO]; - [_textFieldMonth setBordered:NO]; - [_textFieldMonth setDateType:CPMonthDateType]; - [_textFieldMonth setDatePicker:_datePicker]; - [_textFieldMonth setAlignment:CPRightTextAlignment]; - [_textFieldMonth setDatePickerElementView:self]; - [self addSubview:_textFieldMonth]; - - _textFieldYear = [_CPDatePickerElementTextField new]; - [_textFieldYear setBezeled:NO]; - [_textFieldYear setBordered:NO]; - [_textFieldYear setDateType:CPYearDateType]; - [_textFieldYear setDatePicker:_datePicker]; - [_textFieldYear setAlignment:CPRightTextAlignment]; - [_textFieldYear setDatePickerElementView:self]; - [self addSubview:_textFieldYear]; - - _textFieldHour = [_CPDatePickerElementTextField new]; - [_textFieldHour setBezeled:NO]; - [_textFieldHour setBordered:NO]; - [_textFieldHour setDateType:CPHourDateType]; - [_textFieldHour setDatePicker:_datePicker]; - [_textFieldHour setAlignment:CPRightTextAlignment]; - [_textFieldHour setDatePickerElementView:self]; - [self addSubview:_textFieldHour]; - - _textFieldMinute = [_CPDatePickerElementTextField new]; - [_textFieldMinute setBezeled:NO]; - [_textFieldMinute setBordered:NO]; - [_textFieldMinute setDateType:CPMinuteDateType]; - [_textFieldMinute setDatePicker:_datePicker]; - [_textFieldMinute setAlignment:CPRightTextAlignment]; - [_textFieldMinute setDatePickerElementView:self]; - [self addSubview:_textFieldMinute]; - - _textFieldSecond = [_CPDatePickerElementTextField new]; - [_textFieldSecond setBezeled:NO]; - [_textFieldSecond setBordered:NO]; - [_textFieldSecond setDateType:CPSecondDateType]; - [_textFieldSecond setDatePicker:_datePicker]; - [_textFieldSecond setAlignment:CPRightTextAlignment]; - [_textFieldSecond setDatePickerElementView:self]; - [self addSubview:_textFieldSecond]; - - _textFieldPMAM = [_CPDatePickerElementTextField new]; - [_textFieldPMAM setBezeled:NO]; - [_textFieldPMAM setBordered:NO]; - [_textFieldPMAM setDateType:CPAMPMDateType]; - [_textFieldPMAM setDatePicker:_datePicker]; - [_textFieldPMAM setAlignment:CPRightTextAlignment]; - [_textFieldPMAM setDatePickerElementView:self]; - [self addSubview:_textFieldPMAM]; - - _textFieldSeparatorOne = [CPTextField labelWithTitle:@"/"]; - _textFieldSeparatorTwo = [CPTextField labelWithTitle:@"/"]; - _textFieldSeparatorThree = [CPTextField labelWithTitle:@":"]; - _textFieldSeparatorFour = [CPTextField labelWithTitle:@":"]; - - [self addSubview: _textFieldSeparatorOne]; - [self addSubview: _textFieldSeparatorTwo]; - [self addSubview: _textFieldSeparatorThree]; - [self addSubview: _textFieldSeparatorFour]; - - [self setNeedsLayout]; -} - - -#pragma mark - -#pragma mark Responder methods - -/*! @ignore */ -- (BOOL)acceptsFirstResponder -{ - // This is needed to accept to be firstResponder when nothing is selected. - // This element needs to be first responder when the CPDatePicker is in the CPTableView - // When clicking on a row of a CPTableView where there isn't a date element, the CPTableView will ask this element to know if it can becomes or not a firstResponder. - return [_datePicker isEnabled] && ![self superview]._currentTextField; -} - - -#pragma mark - -#pragma mark Override observers - -- (void)_removeObservers -{ - if (!_isObserving) - return; - - [super _removeObservers]; - - [[CPNotificationCenter defaultCenter] removeObserver:self name:CPDatePickerElementTextFieldAMPMChangedNotification object:_textFieldPMAM]; -} - -- (void)_addObservers -{ - if (_isObserving) - return; - - [super _addObservers]; - - [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_datePickerElementTextFieldAMPMChangedNotification:) name:CPDatePickerElementTextFieldAMPMChangedNotification object:_textFieldPMAM]; -} - -#pragma mark - -#pragma mark Mouse event - -- (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint -{ - [self _selectTextFieldForPoint:aPoint]; - return YES; -} - -- (BOOL)startTrackingAt:(CGPoint)aPoint -{ - [self _selectTextFieldForPoint:aPoint]; - return YES; -} - -- (void)_selectTextFieldForPoint:(CGPoint)aPoint -{ - var textField = [self _textFieldForPoint:aPoint], - superview = [self superview]; - - if (!textField || [superview._currentTextField] == textField) - return; - - [[CPNotificationCenter defaultCenter] postNotificationName:CPDatePickerElementTextFieldBecomeFirstResponder object:superview userInfo:[CPDictionary dictionaryWithObject:textField forKey:@"textField"]]; -} - -- (_CPDatePickerElementTextField)_textFieldForPoint:(CGPoint)aPoint -{ - if (![_textFieldDay isHidden] && CGRectContainsPoint([_textFieldDay frame], aPoint)) - return _textFieldDay; - - if (![_textFieldMonth isHidden] && CGRectContainsPoint([_textFieldMonth frame], aPoint)) - return _textFieldMonth; - - if (![_textFieldYear isHidden] && CGRectContainsPoint([_textFieldYear frame], aPoint)) - return _textFieldYear; - - if (![_textFieldHour isHidden] && CGRectContainsPoint([_textFieldHour frame], aPoint)) - return _textFieldHour; - - if (![_textFieldMinute isHidden] && CGRectContainsPoint([_textFieldMinute frame], aPoint)) - return _textFieldMinute; - - if (![_textFieldSecond isHidden] && CGRectContainsPoint([_textFieldSecond frame], aPoint)) - return _textFieldSecond; - - if (![_textFieldPMAM isHidden] && CGRectContainsPoint([_textFieldPMAM frame], aPoint)) - return _textFieldPMAM; - - return nil; -} - -#pragma mark - -#pragma mark Setter Getter methods - -/*! Set the value of the textFields - @param aDateValue the value -*/ -- (void)setDateValue:(CPDate)aDateValue -{ - [_textFieldDay setStringValue:[CPString stringWithFormat:@"%i", aDateValue.getDate()]]; - [_textFieldMonth setStringValue:[CPString stringWithFormat:@"%i", aDateValue.getMonth() + 1]]; - [_textFieldYear setStringValue:[CPString stringWithFormat:@"%i", aDateValue.getFullYear()]]; - [_textFieldHour setStringValue:[CPString stringWithFormat:@"%i", aDateValue.getHours()]]; - [_textFieldMinute setStringValue:[CPString stringWithFormat:@"%i", aDateValue.getMinutes()]]; - [_textFieldSecond setStringValue:[CPString stringWithFormat:@"%i", aDateValue.getSeconds()]]; - - if (aDateValue.getHours() > 11) - [_textFieldPMAM setStringValue:@"PM"]; - else - [_textFieldPMAM setStringValue:@"AM"]; -} - -/*! Set the day date value to the appropriate textField - @param aDayDateValue the day -*/ -- (void)setDayDateValue:(CPString)aDayDateValue -{ - [_textFieldDay setStringValue:aDayDateValue]; -} - -/*! Set the widget enabled or not - @param aBoolean -*/ -- (void)setEnabled:(BOOL)aBoolean -{ - [super setEnabled:aBoolean]; - [_textFieldDay setEnabled:aBoolean]; - [_textFieldMonth setEnabled:aBoolean]; - [_textFieldYear setEnabled:aBoolean]; - [_textFieldHour setEnabled:aBoolean]; - [_textFieldMinute setEnabled:aBoolean]; - [_textFieldSecond setEnabled:aBoolean]; - [_textFieldSeparatorOne setEnabled:aBoolean]; - [_textFieldSeparatorTwo setEnabled:aBoolean]; - [_textFieldSeparatorThree setEnabled:aBoolean]; - [_textFieldSeparatorFour setEnabled:aBoolean]; - [_textFieldPMAM setEnabled:aBoolean]; -} - -/*! Return YES if the hour is set to the morning -*/ -- (BOOL)_isAMHour -{ - return [[_textFieldPMAM stringValue] isEqualToString:@"AM"]; -} - -- (CPDate)dateValue -{ - var date = [[_datePicker dateValue] copy]; - - [date _dateWithTimeZone:[_datePicker timeZone]]; - - if (![_textFieldDay isHidden]) - date.setDate([_textFieldDay intValue]); - - if (![_textFieldMonth isHidden]) - date.setMonth([_textFieldMonth intValue] - 1); - - if (![_textFieldYear isHidden]) - date.setFullYear([_textFieldYear intValue]); - - if (![_textFieldSecond isHidden]) - date.setSeconds([_textFieldSecond intValue]); - - if (![_textFieldMinute isHidden]) - date.setMinutes([_textFieldMinute intValue]); - - if (![_textFieldHour isHidden]) - { - var hour = [_textFieldHour intValue], - currentHour = parseInt(date.getHours()); - - if (hour != currentHour) - { - if ([_datePicker _isAmericanFormat]) - { - if (![self _isAMHour]) - { - if (!(currentHour == 12 && hour == 11) && hour < 13) - hour = hour + 12; - } - else if (hour == 12 && currentHour != 11) - { - hour = 0; - } - else if (currentHour == 0 && hour == 11) - { - hour = 23; - } - else if (hour == 13) - { - hour = 1; - } - } - - if (hour == 24) - hour = 0; - - date.setHours(hour); - } - } - - return date; -} - - -#pragma mark - -#pragma mark Notification methods - -/*! Called when changing AM or PM - @param aNotification -*/ -- (void)_datePickerElementTextFieldAMPMChangedNotification:(CPNotification)aNotification -{ - var value = [[aNotification object] stringValue], - dateValue = [[_datePicker dateValue] copy], - d = [dateValue copy]; - - [d _dateWithTimeZone:[_datePicker timeZone]]; - - if ([value isEqualToString:@"PM"]) - { - if (d.getHours() <= 11) - dateValue.setHours(dateValue.getHours() + 12); - } - else - { - if (d.getHours() > 11) - dateValue.setHours(dateValue.getHours() - 12); - } - - _datePicker._invokedByUserEvent = YES; - [_datePicker _setDateValue:dateValue timeInterval:[_datePicker timeInterval]]; - _datePicker._invokedByUserEvent = NO; -} - - -#pragma mark - -#pragma mark Layout methods - -- (void)layoutSubviews -{ - if ([_datePicker datePickerStyle] == CPClockAndCalendarDatePickerStyle) - return; - - [super layoutSubviews]; - - var themeState = [_datePicker themeState]; - - if ([_datePicker isBezeled] && [_datePicker drawsBackground] || [_datePicker isBordered] && [_datePicker drawsBackground]) - [self setBackgroundColor:[_datePicker valueForThemeAttribute:@"bezel-color" inState:themeState]]; - else if ([_datePicker drawsBackground]) - [self setBackgroundColor:[_datePicker backgroundColor]]; - else - [self setBackgroundColor:[CPColor clearColor]]; - - [self _themeTextFields]; - - [self _updateResponderTextField]; - [self _updateHiddenTextFields]; - [self _setControlSizes]; - [self _sizeToFit]; - [self _updatePositions]; - - [self setNeedsDisplay:YES]; -} - -- (void)drawRect:(CGRect)aRect -{ - [super drawRect:aRect]; - - if (([_datePicker isBordered] || [_datePicker isBezeled]) && ![_datePicker drawsBackground]) - { - var context = [[CPGraphicsContext currentContext] graphicsPort], - borderWidth = [_datePicker valueForThemeAttribute:@"border-width"] / 2, - bezelInset = [_datePicker valueForThemeAttribute:@"bezel-inset" inState:[_datePicker themeState]]; - - CGContextBeginPath(context); - CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:[_datePicker themeState]]); - CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]); - - CGContextMoveToPoint(context, borderWidth - bezelInset.left, borderWidth); - CGContextAddLineToPoint(context, [self bounds].size.width + bezelInset.left - borderWidth, borderWidth); - CGContextAddLineToPoint(context, [self bounds].size.width + bezelInset.left - borderWidth, [self bounds].size.height - borderWidth); - CGContextAddLineToPoint(context, borderWidth - bezelInset.left, [self bounds].size.height - borderWidth); - CGContextAddLineToPoint(context, borderWidth - bezelInset.left, borderWidth); - - CGContextStrokePath(context); - CGContextClosePath(context); - } - -} - -/*! Theme the textFields depending of the theme of the datePicker -*/ -- (void)_themeTextFields -{ - var disabledTextColor = [_datePicker valueForThemeAttribute:@"text-color" inState:CPThemeStateDisabled], - disabledTextFieldBezelColor = [_datePicker valueForThemeAttribute:@"datepicker-textfield-bezel-color" inState:CPThemeStateDisabled], - disabledTextShadowColor = [_datePicker valueForThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled], - disabledTextShadowOffset = [_datePicker valueForThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled], - normalSeparatorContentInset = [_datePicker valueForThemeAttribute:@"content-inset-datepicker-textfield-separator" inState:CPThemeStateNormal], - normalTextFieldBezelColor = [_datePicker valueForThemeAttribute:@"datepicker-textfield-bezel-color" inState:CPThemeStateNormal], - normalTextFieldContentInset = [_datePicker valueForThemeAttribute:@"content-inset-datepicker-textfield" inState:CPThemeStateNormal], - normalTextShadowColor = [_datePicker valueForThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal], - normalTextShadowOffset = [_datePicker valueForThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal], - selectedSeparatorContentInset = [_datePicker valueForThemeAttribute:@"content-inset-datepicker-textfield-separator" inState:CPThemeStateSelected], - selectedTextColor = [_datePicker valueForThemeAttribute:@"text-color" inState:CPThemeStateSelected], - selectedTextFieldBezelColor = [_datePicker valueForThemeAttribute:@"datepicker-textfield-bezel-color" inState:CPThemeStateSelected], - selectedTextFieldContentInset = [_datePicker valueForThemeAttribute:@"content-inset-datepicker-textfield" inState:CPThemeStateSelected], - selectedTextShadowColor = [_datePicker valueForThemeAttribute:@"text-shadow-color" inState:CPThemeStateSelected], - selectedTextShadowOffset = [_datePicker valueForThemeAttribute:@"text-shadow-offset" inState:CPThemeStateSelected], - textColor = [_datePicker textColor], - textFieldMinSize = [_datePicker currentValueForThemeAttribute:@"min-size-datepicker-textfield"], - textFont = [_datePicker textFont]; - - [_textFieldDay setValue:textFieldMinSize forThemeAttribute:@"min-size"]; - [_textFieldDay setValue:normalTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateNormal]; - [_textFieldDay setValue:selectedTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateSelected]; - [_textFieldDay setValue:selectedTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateSelected]; - [_textFieldDay setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateSelected]; - [_textFieldDay setValue:selectedTextColor forThemeAttribute:@"text-color" inState:CPThemeStateSelected]; - [_textFieldDay setValue:selectedTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateSelected]; - [_textFieldDay setValue:selectedTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateSelected]; - [_textFieldDay setValue:normalTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateNormal]; - [_textFieldDay setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateNormal]; - [_textFieldDay setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; - [_textFieldDay setValue:normalTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; - [_textFieldDay setValue:normalTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; - [_textFieldDay setValue:disabledTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateDisabled]; - [_textFieldDay setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateDisabled]; - [_textFieldDay setValue:disabledTextColor forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; - [_textFieldDay setValue:disabledTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; - [_textFieldDay setValue:disabledTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; - - [_textFieldMonth setValue:textFieldMinSize forThemeAttribute:@"min-size"]; - [_textFieldMonth setValue:normalTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateNormal]; - [_textFieldMonth setValue:selectedTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateSelected]; - [_textFieldMonth setValue:selectedTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateSelected]; - [_textFieldMonth setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateSelected]; - [_textFieldMonth setValue:selectedTextColor forThemeAttribute:@"text-color" inState:CPThemeStateSelected]; - [_textFieldMonth setValue:selectedTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateSelected]; - [_textFieldMonth setValue:selectedTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateSelected]; - [_textFieldMonth setValue:normalTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateNormal]; - [_textFieldMonth setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateNormal]; - [_textFieldMonth setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; - [_textFieldMonth setValue:normalTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; - [_textFieldMonth setValue:normalTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; - [_textFieldMonth setValue:disabledTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateDisabled]; - [_textFieldMonth setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateDisabled]; - [_textFieldMonth setValue:disabledTextColor forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; - [_textFieldMonth setValue:disabledTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; - [_textFieldMonth setValue:disabledTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; - - [_textFieldYear setValue:textFieldMinSize forThemeAttribute:@"min-size"]; - [_textFieldYear setValue:normalTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateNormal]; - [_textFieldYear setValue:selectedTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateSelected]; - [_textFieldYear setValue:selectedTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateSelected]; - [_textFieldYear setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateSelected]; - [_textFieldYear setValue:selectedTextColor forThemeAttribute:@"text-color" inState:CPThemeStateSelected]; - [_textFieldYear setValue:selectedTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateSelected]; - [_textFieldYear setValue:selectedTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateSelected]; - [_textFieldYear setValue:normalTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateNormal]; - [_textFieldYear setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateNormal]; - [_textFieldYear setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; - [_textFieldYear setValue:normalTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; - [_textFieldYear setValue:normalTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; - [_textFieldYear setValue:disabledTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateDisabled]; - [_textFieldYear setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateDisabled]; - [_textFieldYear setValue:disabledTextColor forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; - [_textFieldYear setValue:disabledTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; - [_textFieldYear setValue:disabledTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; - - [_textFieldHour setValue:textFieldMinSize forThemeAttribute:@"min-size"]; - [_textFieldHour setValue:normalTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateNormal]; - [_textFieldHour setValue:selectedTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateSelected]; - [_textFieldHour setValue:selectedTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateSelected]; - [_textFieldHour setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateSelected]; - [_textFieldHour setValue:selectedTextColor forThemeAttribute:@"text-color" inState:CPThemeStateSelected]; - [_textFieldHour setValue:selectedTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateSelected]; - [_textFieldHour setValue:selectedTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateSelected]; - [_textFieldHour setValue:normalTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateNormal]; - [_textFieldHour setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateNormal]; - [_textFieldHour setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; - [_textFieldHour setValue:normalTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; - [_textFieldHour setValue:normalTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; - [_textFieldHour setValue:disabledTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateDisabled]; - [_textFieldHour setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateDisabled]; - [_textFieldHour setValue:disabledTextColor forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; - [_textFieldHour setValue:disabledTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; - [_textFieldHour setValue:disabledTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; - - [_textFieldMinute setValue:textFieldMinSize forThemeAttribute:@"min-size"]; - [_textFieldMinute setValue:normalTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateNormal]; - [_textFieldMinute setValue:selectedTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateSelected]; - [_textFieldMinute setValue:selectedTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateSelected]; - [_textFieldMinute setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateSelected]; - [_textFieldMinute setValue:selectedTextColor forThemeAttribute:@"text-color" inState:CPThemeStateSelected]; - [_textFieldMinute setValue:selectedTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateSelected]; - [_textFieldMinute setValue:selectedTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateSelected]; - [_textFieldMinute setValue:normalTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateNormal]; - [_textFieldMinute setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateNormal]; - [_textFieldMinute setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; - [_textFieldMinute setValue:normalTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; - [_textFieldMinute setValue:normalTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; - [_textFieldMinute setValue:disabledTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateDisabled]; - [_textFieldMinute setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateDisabled]; - [_textFieldMinute setValue:disabledTextColor forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; - [_textFieldMinute setValue:disabledTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; - [_textFieldMinute setValue:disabledTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; - - [_textFieldSecond setValue:textFieldMinSize forThemeAttribute:@"min-size"]; - [_textFieldSecond setValue:normalTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateNormal]; - [_textFieldSecond setValue:selectedTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateSelected]; - [_textFieldSecond setValue:selectedTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateSelected]; - [_textFieldSecond setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateSelected]; - [_textFieldSecond setValue:selectedTextColor forThemeAttribute:@"text-color" inState:CPThemeStateSelected]; - [_textFieldSecond setValue:selectedTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateSelected]; - [_textFieldSecond setValue:selectedTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateSelected]; - [_textFieldSecond setValue:normalTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateNormal]; - [_textFieldSecond setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateNormal]; - [_textFieldSecond setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; - [_textFieldSecond setValue:normalTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; - [_textFieldSecond setValue:normalTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; - [_textFieldSecond setValue:disabledTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateDisabled]; - [_textFieldSecond setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateDisabled]; - [_textFieldSecond setValue:disabledTextColor forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; - [_textFieldSecond setValue:disabledTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; - [_textFieldSecond setValue:disabledTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; - - [_textFieldPMAM setValue:textFieldMinSize forThemeAttribute:@"min-size"]; - [_textFieldPMAM setValue:normalTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateNormal]; - [_textFieldPMAM setValue:selectedTextFieldContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateSelected]; - [_textFieldPMAM setValue:selectedTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateSelected]; - [_textFieldPMAM setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateSelected]; - [_textFieldPMAM setValue:selectedTextColor forThemeAttribute:@"text-color" inState:CPThemeStateSelected]; - [_textFieldPMAM setValue:selectedTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateSelected]; - [_textFieldPMAM setValue:selectedTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateSelected]; - [_textFieldPMAM setValue:normalTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateNormal]; - [_textFieldPMAM setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateNormal]; - [_textFieldPMAM setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; - [_textFieldPMAM setValue:normalTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; - [_textFieldPMAM setValue:normalTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; - [_textFieldPMAM setValue:disabledTextFieldBezelColor forThemeAttribute:@"bezel-color" inState:CPThemeStateDisabled]; - [_textFieldPMAM setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateDisabled]; - [_textFieldPMAM setValue:disabledTextColor forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; - [_textFieldPMAM setValue:disabledTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; - [_textFieldPMAM setValue:disabledTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; - - [_textFieldSeparatorOne setValue:normalSeparatorContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateNormal]; - [_textFieldSeparatorOne setValue:selectedSeparatorContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateSelected]; - [_textFieldSeparatorOne setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateNormal]; - [_textFieldSeparatorOne setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; - [_textFieldSeparatorOne setValue:normalTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; - [_textFieldSeparatorOne setValue:normalTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; - [_textFieldSeparatorOne setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateDisabled]; - [_textFieldSeparatorOne setValue:disabledTextColor forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; - [_textFieldSeparatorOne setValue:disabledTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; - [_textFieldSeparatorOne setValue:disabledTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; - - [_textFieldSeparatorTwo setValue:normalSeparatorContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateNormal]; - [_textFieldSeparatorTwo setValue:selectedSeparatorContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateSelected]; - [_textFieldSeparatorTwo setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateNormal]; - [_textFieldSeparatorTwo setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; - [_textFieldSeparatorTwo setValue:normalTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; - [_textFieldSeparatorTwo setValue:normalTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; - [_textFieldSeparatorTwo setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateDisabled]; - [_textFieldSeparatorTwo setValue:disabledTextColor forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; - [_textFieldSeparatorTwo setValue:disabledTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; - [_textFieldSeparatorTwo setValue:disabledTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; - - [_textFieldSeparatorThree setValue:normalSeparatorContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateNormal]; - [_textFieldSeparatorThree setValue:selectedSeparatorContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateSelected]; - [_textFieldSeparatorThree setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateNormal]; - [_textFieldSeparatorThree setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; - [_textFieldSeparatorThree setValue:normalTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; - [_textFieldSeparatorThree setValue:normalTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; - [_textFieldSeparatorThree setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateDisabled]; - [_textFieldSeparatorThree setValue:disabledTextColor forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; - [_textFieldSeparatorThree setValue:disabledTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; - [_textFieldSeparatorThree setValue:disabledTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; - - [_textFieldSeparatorFour setValue:normalSeparatorContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateNormal]; - [_textFieldSeparatorFour setValue:selectedSeparatorContentInset forThemeAttribute:@"content-inset" inState:CPThemeStateSelected]; - [_textFieldSeparatorFour setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateNormal]; - [_textFieldSeparatorFour setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; - [_textFieldSeparatorFour setValue:normalTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; - [_textFieldSeparatorFour setValue:normalTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal]; - [_textFieldSeparatorFour setValue:textFont forThemeAttribute:@"font" inState:CPThemeStateDisabled]; - [_textFieldSeparatorFour setValue:disabledTextColor forThemeAttribute:@"text-color" inState:CPThemeStateDisabled]; - [_textFieldSeparatorFour setValue:disabledTextShadowColor forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled]; - [_textFieldSeparatorFour setValue:disabledTextShadowOffset forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled]; -} - -/*! Hide or not the textField depending on the datePickerElements flag -*/ -- (void)_updateHiddenTextFields -{ - var datePickerElements = [_datePicker datePickerElements], - isAmericanFormat = [_datePicker _isAmericanFormat]; - - if (datePickerElements & CPYearMonthDatePickerElementFlag) - { - [_textFieldDay setHidden:YES]; - [_textFieldMonth setHidden:NO]; - [_textFieldYear setHidden:NO]; - [_textFieldSeparatorTwo setHidden:NO]; - [_textFieldSeparatorOne setHidden:YES]; - - if (([datePickerElements & CPYearMonthDayDatePickerElementFlag]) == CPYearMonthDayDatePickerElementFlag) - { - [_textFieldDay setHidden:NO]; - [_textFieldSeparatorOne setHidden:NO]; - } - } - else - { - [_textFieldMonth setHidden:YES]; - [_textFieldYear setHidden:YES]; - [_textFieldDay setHidden:YES]; - [_textFieldSeparatorTwo setHidden:YES]; - [_textFieldSeparatorOne setHidden:YES]; - } - - if (datePickerElements & CPHourMinuteDatePickerElementFlag) - { - [_textFieldHour setHidden:NO]; - [_textFieldMinute setHidden:NO]; - [_textFieldSecond setHidden:YES]; - [_textFieldSeparatorThree setHidden:NO]; - [_textFieldSeparatorFour setHidden:YES]; - - if (isAmericanFormat) - [_textFieldPMAM setHidden:NO]; - else - [_textFieldPMAM setHidden:YES]; - - if ((datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag) - { - [_textFieldSecond setHidden:NO]; - [_textFieldSeparatorFour setHidden:NO]; - } - } - else - { - [_textFieldHour setHidden:YES]; - [_textFieldMinute setHidden:YES]; - [_textFieldSecond setHidden:YES]; - [_textFieldSeparatorThree setHidden:YES]; - [_textFieldSeparatorFour setHidden:YES]; - [_textFieldPMAM setHidden:YES]; - } -} - -/*! Update the position of the textField depending on the datePickerElements flag -*/ -- (void)_updatePositions -{ - var contentInset = [_datePicker valueForThemeAttribute:@"content-inset" inState:[_datePicker themeState]], - separatorContentInset = [_datePicker valueForThemeAttribute:@"separator-content-inset"], - horizontalInset = contentInset.left - contentInset.right, - verticalInset = contentInset.top - contentInset.bottom, - firstTexField = _textFieldMonth, - secondTextField = _textFieldDay, - isAmericanFormat = [_datePicker _isAmericanFormat]; - - if (!isAmericanFormat) - { - firstTexField = _textFieldDay; - secondTextField = _textFieldMonth; - } - - [firstTexField setFrameOrigin:CGPointMake(horizontalInset,verticalInset)]; - [_textFieldSeparatorOne setFrameOrigin:CGPointMake(CGRectGetMaxX([firstTexField frame]) + separatorContentInset.left, verticalInset)]; - - if ([firstTexField isHidden]) - [secondTextField setFrameOrigin:CGPointMake(horizontalInset,verticalInset)]; - else - [secondTextField setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldSeparatorOne frame]) + separatorContentInset.right, verticalInset)]; - - if (isAmericanFormat && [secondTextField isHidden]) - [_textFieldSeparatorTwo setFrameOrigin:CGPointMake(CGRectGetMaxX([firstTexField frame]) + separatorContentInset.left, verticalInset)]; - else - [_textFieldSeparatorTwo setFrameOrigin:CGPointMake(CGRectGetMaxX([secondTextField frame]) + separatorContentInset.left, verticalInset)]; - - [_textFieldYear setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldSeparatorTwo frame]) + separatorContentInset.right, verticalInset)]; - - if ([_textFieldMonth isHidden]) - [_textFieldHour setFrameOrigin:CGPointMake(horizontalInset, verticalInset)]; - else - [_textFieldHour setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldYear frame]) + [_datePicker currentValueForThemeAttribute:@"date-hour-margin"], verticalInset)]; - - [_textFieldSeparatorThree setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldHour frame]) + separatorContentInset.left, verticalInset)]; - [_textFieldMinute setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldSeparatorThree frame]) + separatorContentInset.right, verticalInset)]; - [_textFieldSeparatorFour setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldMinute frame]) + separatorContentInset.left, verticalInset)]; - [_textFieldSecond setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldSeparatorFour frame]) + separatorContentInset.right, verticalInset)]; - - if ([_textFieldSecond isHidden]) - [_textFieldPMAM setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldMinute frame]) + 2, verticalInset)]; - else - [_textFieldPMAM setFrameOrigin:CGPointMake(CGRectGetMaxX([_textFieldSecond frame]) + 2, verticalInset)]; -} - -/*! Size to fit all of the textFields -*/ -- (void)_sizeToFit -{ - [_textFieldDay sizeToFit]; - [_textFieldMonth sizeToFit]; - [_textFieldYear sizeToFit]; - [_textFieldHour sizeToFit]; - [_textFieldMinute sizeToFit]; - [_textFieldSecond sizeToFit]; - [_textFieldSeparatorOne sizeToFit]; - [_textFieldSeparatorTwo sizeToFit]; - [_textFieldSeparatorThree sizeToFit]; - [_textFieldSeparatorFour sizeToFit]; - [_textFieldPMAM sizeToFit]; -} - -/*! Size to fit all of the textFields -*/ -- (void)_setControlSizes -{ - var controlSize = [_datePicker controlSize]; - - [_textFieldDay setControlSize:controlSize]; - [_textFieldMonth setControlSize:controlSize]; - [_textFieldYear setControlSize:controlSize]; - [_textFieldHour setControlSize:controlSize]; - [_textFieldMinute setControlSize:controlSize]; - [_textFieldSecond setControlSize:controlSize]; - [_textFieldSeparatorOne setControlSize:controlSize]; - [_textFieldSeparatorTwo setControlSize:controlSize]; - [_textFieldSeparatorThree setControlSize:controlSize]; - [_textFieldSeparatorFour setControlSize:controlSize]; - [_textFieldPMAM setControlSize:controlSize]; -} - - -#pragma mark - -#pragma mark Responder methods - -- (void)_updateResponderTextField -{ - [self _updateFirstLastTextField]; - [self _updateKeyView]; -} - -/*! Update the var _firstTextField and _lastTextField -*/ -- (void)_updateFirstLastTextField -{ - var datePickerElements = [_datePicker datePickerElements]; - - if ([_datePicker _isAmericanFormat]) - { - if (datePickerElements & CPYearMonthDayDatePickerElementFlag || datePickerElements & CPYearMonthDatePickerElementFlag) - [[self superview] setFirstTextField:_textFieldMonth]; - else - [[self superview] setFirstTextField:_textFieldHour]; - - if (datePickerElements & CPHourMinuteSecondDatePickerElementFlag || datePickerElements & CPHourMinuteDatePickerElementFlag) - [[self superview] setLastTextField:_textFieldPMAM]; - else - [[self superview] setLastTextField:_textFieldYear]; - } - else - { - if ((datePickerElements & CPYearMonthDayDatePickerElementFlag) == CPYearMonthDayDatePickerElementFlag) - [[self superview] setFirstTextField:_textFieldDay]; - else if (datePickerElements & CPYearMonthDayDatePickerElementFlag || datePickerElements & CPYearMonthDatePickerElementFlag) - [[self superview] setFirstTextField:_textFieldMonth]; - else - [[self superview] setFirstTextField:_textFieldHour]; - - if ((datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag) - [[self superview] setLastTextField:_textFieldSecond]; - else if (datePickerElements & CPHourMinuteSecondDatePickerElementFlag || datePickerElements & CPHourMinuteDatePickerElementFlag) - [[self superview] setLastTextField:_textFieldMinute]; - else - [[self superview] setLastTextField:_textFieldYear]; - } -} - -/*! Update the nextTextField params of all of the textField. This is used to move the current textField with the arrows -*/ -- (void)_updateKeyView -{ - [self _updateNextTextField]; - [self _updatePreviousTextField] -} - -- (void)_updateNextTextField -{ - var datePickerElements = [_datePicker datePickerElements], - firstTexField = _textFieldMonth, - secondTextField = _textFieldDay, - isAmericanFormat = [_datePicker _isAmericanFormat]; - - if (!isAmericanFormat) - { - firstTexField = _textFieldDay; - secondTextField = _textFieldMonth; - } - - if ((datePickerElements & CPYearMonthDayDatePickerElementFlag) == CPYearMonthDayDatePickerElementFlag) - [firstTexField setNextTextField:secondTextField]; - else - [firstTexField setNextTextField:_textFieldYear]; - - [secondTextField setNextTextField:_textFieldYear]; - - if (datePickerElements & CPHourMinuteSecondDatePickerElementFlag || datePickerElements & CPHourMinuteDatePickerElementFlag) - [_textFieldYear setNextTextField:_textFieldHour]; - else if (isAmericanFormat || (datePickerElements & CPYearMonthDayDatePickerElementFlag) == CPYearMonthDayDatePickerElementFlag) - [_textFieldYear setNextTextField:firstTexField]; - else - [_textFieldYear setNextTextField:secondTextField]; - - [_textFieldHour setNextTextField:_textFieldMinute]; - - if ((datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag) - [_textFieldMinute setNextTextField:_textFieldSecond]; - else if (isAmericanFormat) - [_textFieldMinute setNextTextField:_textFieldPMAM]; - else if ((datePickerElements & CPYearMonthDayDatePickerElementFlag) == CPYearMonthDayDatePickerElementFlag) - [_textFieldMinute setNextTextField:firstTexField]; - else if (datePickerElements & CPYearMonthDatePickerElementFlag) - [_textFieldMinute setNextTextField:secondTextField]; - else - [_textFieldMinute setNextTextField:_textFieldHour]; - - if (isAmericanFormat) - [_textFieldSecond setNextTextField:_textFieldPMAM]; - else if ((datePickerElements & CPYearMonthDayDatePickerElementFlag) == CPYearMonthDayDatePickerElementFlag) - [_textFieldSecond setNextTextField:firstTexField]; - else if (datePickerElements & CPYearMonthDatePickerElementFlag) - [_textFieldSecond setNextTextField:secondTextField]; - else - [_textFieldSecond setNextTextField:_textFieldHour]; - - if (datePickerElements & CPYearMonthDayDatePickerElementFlag) - [_textFieldPMAM setNextTextField:_textFieldMonth]; - else - [_textFieldPMAM setNextTextField:_textFieldHour]; -} - -- (void)_updatePreviousTextField -{ - var datePickerElements = [_datePicker datePickerElements], - firstTexField = _textFieldMonth, - secondTextField = _textFieldDay, - isAmericanFormat = [_datePicker _isAmericanFormat]; - - if (!isAmericanFormat) - { - firstTexField = _textFieldDay; - secondTextField = _textFieldMonth; - } - - if ((datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag) - [_textFieldPMAM setPreviousTextField:_textFieldSecond]; - else if (datePickerElements & CPHourMinuteDatePickerElementFlag) - [_textFieldPMAM setPreviousTextField:_textFieldMinute]; - - [_textFieldSecond setPreviousTextField:_textFieldMinute]; - [_textFieldMinute setPreviousTextField:_textFieldHour]; - - if (datePickerElements & CPYearMonthDatePickerElementFlag) - [_textFieldHour setPreviousTextField:_textFieldYear]; - else if (isAmericanFormat) - [_textFieldHour setPreviousTextField:_textFieldPMAM]; - else if ((datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag) - [_textFieldHour setPreviousTextField:_textFieldSecond]; - else - [_textFieldHour setPreviousTextField:_textFieldMinute]; - - if (!isAmericanFormat) - [_textFieldYear setPreviousTextField:_textFieldMonth]; - else if ((datePickerElements & CPYearMonthDayDatePickerElementFlag) == CPYearMonthDayDatePickerElementFlag) - [_textFieldYear setPreviousTextField:_textFieldDay]; - else - [_textFieldYear setPreviousTextField:_textFieldMonth]; - - [secondTextField setPreviousTextField:firstTexField]; - - if (isAmericanFormat && datePickerElements & CPHourMinuteDatePickerElementFlag) - [firstTexField setPreviousTextField:_textFieldPMAM]; - else if ((datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag) - [firstTexField setPreviousTextField:_textFieldSecond]; - else if (datePickerElements & CPHourMinuteDatePickerElementFlag) - [firstTexField setPreviousTextField:_textFieldMinute]; - else - [firstTexField setPreviousTextField:_textFieldYear]; -} - -@end - - -var CPMonthDateType = 0, - CPDayDateType = 1, - CPYearDateType = 2, - CPHourDateType = 3, - CPSecondDateType = 4, - CPMinuteDateType = 5, - CPAMPMDateType = 6; - -/*! An element textField -*/ -@implementation _CPDatePickerElementTextField : CPTextField -{ - _CPDatePickerElementTextField _nextTextField @accessors(property=nextTextField); - _CPDatePickerElementTextField _previousTextField @accessors(property=previousTextField); - _CPDatePickerElementView _datePickerElementView @accessors(property=datePickerElementView); - - CPDatePicker _datePicker @accessors(setter=setDatePicker:); - - int _dateType @accessors(getter=dateType); - int _maxNumber @accessors(getter=maxNumber); - int _minNumber @accessors(getter=minNumber); - - BOOL _firstEvent; - CPTimer _timerEdition; -} - -- (id)init -{ - if (self = [super init]) - { - _firstEvent = YES; - } - - return self; -} - -/*! @ignore */ -- (BOOL)acceptsFirstResponder -{ - return [_datePicker isEnabled]; -} - -/*! Set the dateType of the textField -*/ -- (void)setDateType:(int)aDateType -{ - _dateType = aDateType; - - switch (aDateType) - { - case CPMonthDateType: - _minNumber = 1; - _maxNumber = 12; - break; - - case CPDayDateType: - _minNumber = 1; - _maxNumber = 31; - break; - - case CPYearDateType: - _minNumber = 0; - _maxNumber = 9999; - break; - - case CPHourDateType: - _minNumber = 0; - _maxNumber = 23; - break; - - case CPSecondDateType: - _minNumber = 0; - _maxNumber = 59; - break; - - case CPMinuteDateType: - _minNumber = 0; - _maxNumber = 59; - break; - } -} - -/*! Return the maxNumber of the textField -*/ -- (int)maxNumber -{ - if (_dateType == CPDayDateType) - return [[_datePicker dateValue] _daysInMonth]; - - return _maxNumber; -} - -/*! Return the maxNumber of the textField depending of the maxDate -*/ -- (int)_maxNumberWithMaxDate -{ - var maxDate = [_datePicker maxDate], - date = [_datePicker dateValue]; - - if (maxDate) - { - switch (_dateType) - { - case CPMonthDateType: - if (maxDate.getFullYear() == date.getFullYear()) - return maxDate.getMonth(); - break; - - case CPDayDateType: - if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth()) - return maxDate.getDate(); - break; - - case CPYearDateType: - return maxDate.getFullYear(); - - case CPHourDateType: - if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth() && maxDate.getDate() == date.getDate()) - return maxDate.getHours(); - break; - - case CPSecondDateType: - if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth() && maxDate.getDate() == date.getDate() && maxDate.getHours() == date.getHours() && maxDate.getMinutes() == date.getMinutes()) - return maxDate.getSeconds(); - break; - - case CPMinuteDateType: - if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth() && maxDate.getDate() == date.getDate() && maxDate.getHours() == date.getHours()) - return maxDate.getMinutes(); - break; - } - } - - return _maxNumber; -} - -/*! Set the stringValue of the textField. This is going to check if there is 2 or 4 letters. If not it adds a space. Check also the maxDate - It's called when the user is editing with the keyboard - @param aStringValue a CPString -*/ -- (void)setValueForKeyEvent:(CPEvent)anEvent -{ - var keyCode = [anEvent keyCode]; - - if (keyCode != CPDeleteKeyCode && keyCode != CPDeleteForwardKeyCode && keyCode < CPZeroKeyCode || keyCode > CPNineKeyCode) - return; - - var newValue = [self stringValue].replace(/\s/g, ''), - length = [newValue length], - eventKeyValue = parseInt([anEvent characters]).toString(); - - if (keyCode == CPDeleteKeyCode || keyCode == CPDeleteForwardKeyCode) - { - [_timerEdition invalidate]; - _timerEdition = nil; - newValue = [newValue substringToIndex:(length - 1)]; - } - else - { - if (!_timerEdition) - { - _timerEdition = [CPTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(_timerKeyEvent:) userInfo:nil repeats:NO]; - - if (_firstEvent || !length) - newValue = eventKeyValue; - else - newValue = parseInt(newValue).toString() + eventKeyValue; - } - else - { - var newFireDate = [CPDate date]; - - newFireDate.setSeconds(newFireDate.getSeconds() + 2); - [_timerEdition setFireDate:newFireDate]; - - newValue = parseInt(newValue).toString() + eventKeyValue; - } - } - - if (parseInt(newValue) > [self _maxNumberWithMaxDate] || ([_datePicker _isAmericanFormat] && _dateType == CPHourDateType && parseInt(newValue) > 12)) - return; - - _firstEvent = NO; - - [super setObjectValue:newValue]; -} - -/*! - End of the timer -*/ -- (void)_timerKeyEvent:(id)sender -{ - var stringValue = [self stringValue]; - - _timerEdition = nil; - - if ([stringValue length]) - { - if ([_datePicker _isAmericanFormat] && [self dateType] == CPHourDateType) - { - var isAMHour = [[self superview] _isAMHour]; - - if (!isAMHour && stringValue != 12) - stringValue = parseInt(stringValue) + 12; - - if (stringValue == 12 && !isAMHour) - stringValue = 12; - else if (stringValue == 12) - stringValue = 0; - } - - [self setObjectValue:stringValue]; - } -} - -/*! - We force to end the timer -*/ -- (void)_invalidTimer -{ - if (_timerEdition) - { - [_timerEdition invalidate]; - _timerEdition = nil; - } -} - -/*! - We force to end the timer and to update the objectValue of the datePicker -*/ -- (void)_endEditing -{ - if (_timerEdition) - [_timerEdition invalidate]; - - _timerEdition = nil; - - var objectValue = [self stringValue]; - - if (![objectValue length]) - objectValue = [self objectValue]; - - if ([_datePicker _isAmericanFormat] && [self dateType] == CPHourDateType) - { - var isAMHour = [[self superview] _isAMHour]; - - if (!isAMHour && objectValue != 12) - objectValue = parseInt(objectValue) + 12; - - if (objectValue == 12 && !isAMHour) - objectValue = 12; - else if (objectValue == 12) - objectValue = 0; - } - - [self setObjectValue:objectValue]; -} - -/*! Set the stringValue of the TextField. Add some zeros of there isn't 2/4 letters in the value. It's called at the end of the editing process - @param aStringValue a CPString -*/ -- (void)setStringValue:(CPString)aStringValue -{ - if (_dateType == CPYearDateType) - { - while ([aStringValue length] < 4) - aStringValue = "0" + aStringValue; - } - else if (_dateType != CPAMPMDateType) - { - if (_dateType == CPHourDateType && [_datePicker _isAmericanFormat]) - { - var value = parseInt(aStringValue); - - if (value == 0) - value = 12; - else if (value > 12) - value = value - 12; - - aStringValue = value.toString(); - } - - while ([aStringValue length] < 2) - { - if (_dateType == CPSecondDateType || _dateType == CPMinuteDateType) - aStringValue = @"0" + aStringValue; - else - aStringValue = @" " + aStringValue; - } - - } - - [super setObjectValue:aStringValue]; -} - -/*! Set the objectValue. This will update the dateValue of the datePicker also. It's called with the binding of the stepper or arrows - This is not going to update the objectValue of the control !!! It updates the dateValue of the datePicker who's going to update the datePickerTextField if necessary - It's a bit tricky - @param aObjectValue -*/ -- (void)setObjectValue:(id)anObjectValue -{ - var dateValue = [[_datePicker dateValue] copy], - lengthString = [[self stringValue] length], - objectValue = parseInt(anObjectValue); - - switch (_dateType) - { - case CPMonthDateType: - - if (objectValue == 0 || !lengthString) - { - [self setStringValue:(dateValue.getMonth() + 1).toString()]; - return; - } - - var dateNextMonth = [dateValue copy]; - - dateNextMonth.setDate(1); - dateNextMonth.setMonth(parseInt(anObjectValue) - 1); - - var numberDayNextMonth = [dateNextMonth _daysInMonth]; - - if (numberDayNextMonth < [dateValue _daysInMonth] && dateValue.getDate() > numberDayNextMonth) - [_datePickerElementView setDayDateValue:numberDayNextMonth.toString()]; - - [super setObjectValue:objectValue]; - break; - - case CPDayDateType: - - if (objectValue == 0 || !lengthString) - { - [self setStringValue:dateValue.getDate().toString()]; - return; - } - - [super setObjectValue:objectValue]; - break; - - case CPYearDateType: - - if (objectValue == 0 || !lengthString) - { - [self setStringValue:dateValue.getFullYear().toString()]; - return; - } - - [super setObjectValue:objectValue]; - break; - - case CPHourDateType: - - if (!lengthString) - { - [self setStringValue:dateValue.getHours().toString()]; - return; - } - - [super setObjectValue:objectValue]; - break; - - case CPSecondDateType: - - if (!lengthString) - { - [self setStringValue:dateValue.getSeconds().toString()]; - return; - } - - [super setObjectValue:objectValue]; - break; - - case CPMinuteDateType: - - if (!lengthString) - { - [self setStringValue:dateValue.getMinutes().toString()]; - return; - } - - [super setObjectValue:objectValue]; - break; - } - - var newDateValue = [_datePickerElementView dateValue], - timeZone = [_datePicker timeZone]; - - if (timeZone) - { - var secondsFromGMT = [timeZone secondsFromGMTForDate:newDateValue], - secondsFromGMTTimeZone = [timeZone secondsFromGMT]; - - newDateValue.setSeconds(newDateValue.getSeconds() + secondsFromGMT - secondsFromGMTTimeZone); - } - - _datePicker._invokedByUserEvent = YES; - [_datePicker _setDateValue:newDateValue timeInterval:[_datePicker timeInterval]]; - _datePicker._invokedByUserEvent = NO; -} - - -#pragma mark - -#pragma mark Mouse event - -/*! Mouse down event. Launch a notification to notif the new first responder textField -*/ -- (void)mouseDown:(CPEvent)anEvent -{ - if (![self isEnabled]) - return; - - [super mouseDown:anEvent]; - [[CPNotificationCenter defaultCenter] postNotificationName:CPDatePickerElementTextFieldBecomeFirstResponder object:[[self superview] superview] userInfo:[CPDictionary dictionaryWithObject:self forKey:@"textField"]]; -} - - -#pragma mark - -#pragma mark Theme functions - -/*! Set the theme CPThemeStateSelected -*/ -- (void)makeSelectable -{ - [self setThemeState:CPThemeStateSelected]; - [_datePicker setThemeState:CPThemeStateEditing]; -} - -/*! Unsert the theme CPThemeStateSelected -*/ -- (void)makeDeselectable -{ - _firstEvent = YES; - [self unsetThemeState:CPThemeStateSelected]; - [_datePicker unsetThemeState:CPThemeStateEditing]; -} - - -#pragma mark - -#pragma mark Override - -/*! - We override this method to get all the time the good width -*/ -- (CGSize)_minimumFrameSize -{ - var frameSize = [self frameSize], - contentInset = [self currentValueForThemeAttribute:@"content-inset"], - minSize = [self currentValueForThemeAttribute:@"min-size"], - maxSize = [self currentValueForThemeAttribute:@"max-size"], - lineBreakMode = [self lineBreakMode], - text = (_dateType == CPYearDateType) ? @"0000" : @"00", - textSize = CGSizeMakeCopy(frameSize), - font = [self currentValueForThemeAttribute:@"font"]; - - textSize.width -= contentInset.left + contentInset.right; - textSize.height -= contentInset.top + contentInset.bottom; - - if (_dateType == CPAMPMDateType) - text = [self stringValue]; - - if (frameSize.width !== 0 && - ![self isBezeled] && - (lineBreakMode === CPLineBreakByWordWrapping || lineBreakMode === CPLineBreakByCharWrapping)) - { - textSize = [text sizeWithFont:font inWidth:textSize.width]; - } - else - { - textSize = [text sizeWithFont:font]; - - // Account for possible fractional pixels at right edge - textSize.width += 1; - } - - // Account for possible fractional pixels at bottom edge - textSize.height += 1; - - frameSize.height = textSize.height + contentInset.top + contentInset.bottom; - - if ([self isBezeled]) - { - frameSize.height = MAX(frameSize.height, minSize.height); - - if (maxSize.width > 0.0) - frameSize.width = MIN(frameSize.width, maxSize.width); - - if (maxSize.height > 0.0) - frameSize.height = MIN(frameSize.height, maxSize.height); - } - else - frameSize.width = textSize.width + contentInset.left + contentInset.right; - - frameSize.width = MAX(frameSize.width, minSize.width); - - return frameSize; -} - -- (CGRect)bezelRectForBounds:(CGRect)bounds -{ - return CGRectMakeCopy(bounds); -} - -@end diff --git a/AppKit/CPTheme.j b/AppKit/CPTheme.j index c232236d8..a5a14af34 100644 --- a/AppKit/CPTheme.j +++ b/AppKit/CPTheme.j @@ -814,6 +814,8 @@ CPThemeStateKeyWindow = CPThemeState("keyWindow"); CPThemeStateControlSizeRegular = CPThemeState("controlSizeRegular"); CPThemeStateControlSizeSmall = CPThemeState("controlSizeSmall"); CPThemeStateControlSizeMini = CPThemeState("controlSizeMini"); +CPThemeStateAlternateState = CPThemeState("alternate"); +CPThemeStateComposedControl = CPThemeState("composed"); CPThemeStateNormalString = String(CPThemeStateNormal); diff --git a/Tools/nib2cib/NSDatePicker.j b/Tools/nib2cib/NSDatePicker.j index c14ab8c55..92b304564 100644 --- a/Tools/nib2cib/NSDatePicker.j +++ b/Tools/nib2cib/NSDatePicker.j @@ -50,23 +50,24 @@ var NSDatePickerDefaultSize = 22, _timeInterval = [cell timeInterval]; _datePickerMode = [cell datePickerMode]; _datePickerElements = [cell datePickerElements]; - _datePickerStyle = [cell datePickerType]; _dateValue = [cell objectValue]; - _backgroundColor = [cell backgroundColor]; - _drawsBackground = [cell drawsBackground]; _formatter = [cell formatter]; [self setBezeled:[cell isBezeled]]; [self setBordered:[cell isBordered]]; [self setEnabled:[cell isEnabled]]; [self setControlSize:[cell controlSize]]; + [self setDatePickerStyle:[cell datePickerType]]; + [self setDrawsBackground:[cell drawsBackground]]; + [self setBackgroundColor:[cell backgroundColor]]; - var theme = [Nib2Cib defaultTheme]; + var theme = [Nib2Cib defaultTheme], + state = [self themeState]; if (_datePickerStyle != CPClockAndCalendarDatePickerStyle) { - var minSize = [theme valueForAttributeWithName:@"min-size" forClass:[self class]], - maxSize = [theme valueForAttributeWithName:@"max-size" forClass:[self class]]; + var minSize = [theme valueForAttributeWithName:@"min-size" inState:state forClass:[self class]], + maxSize = [theme valueForAttributeWithName:@"max-size" inState:state forClass:[self class]]; if (minSize.height > 0) { @@ -94,38 +95,44 @@ var NSDatePickerDefaultSize = 22, } else { - var minSize = [theme valueForAttributeWithName:@"min-size-calendar" forClass:[self class]], - maxSize = [theme valueForAttributeWithName:@"max-size-calendar" forClass:[self class]], - sizeClock = [theme valueForAttributeWithName:@"size-clock" forClass:[self class]]; + var isClockOnly = (_datePickerStyle === CPClockAndCalendarDatePickerStyle) && ((_datePickerElements & CPHourMinuteSecondDatePickerElementFlag) || (_datePickerElements & CPHourMinuteDatePickerElementFlag)) && !((_datePickerElements & CPYearMonthDayDatePickerElementFlag) || (_datePickerElements & CPYearMonthDatePickerElementFlag)); - if (_datePickerElements & CPHourMinuteSecondDatePickerElementFlag || _datePickerElements & CPHourMinuteDatePickerElementFlag) + if (!isClockOnly) { - minSize.width += sizeClock.width + 10; - maxSize.width += sizeClock.width + 10; - } + var minSize = [theme valueForAttributeWithName:@"min-size-calendar" forClass:[self class]], + maxSize = [theme valueForAttributeWithName:@"max-size-calendar" forClass:[self class]], + sizeClock = [theme valueForAttributeWithName:@"size-clock" forClass:[self class]], + calendarClockMargin = [theme valueForAttributeWithName:@"calendar-clock-margin" forClass:[self class]]; - if (minSize.height > 0) - { - _frame.size.height = MAX(_frame.size.height, minSize.height); - _bounds.size.height = MAX(_frame.size.height, minSize.height); - } + if (_datePickerElements & CPHourMinuteSecondDatePickerElementFlag || _datePickerElements & CPHourMinuteDatePickerElementFlag) + { + minSize.width += sizeClock.width + calendarClockMargin; + maxSize.width += sizeClock.width + calendarClockMargin; + } - if (maxSize.height > 0) - { - _frame.size.height = MIN(_frame.size.height, maxSize.height); - _bounds.size.height = MIN(_frame.size.height, maxSize.height); - } + if (minSize.height > 0) + { + _frame.size.height = MAX(_frame.size.height, minSize.height); + _bounds.size.height = MAX(_frame.size.height, minSize.height); + } - if (minSize.width > 0) - { - _frame.size.width = MAX(_frame.size.width, minSize.width); - _bounds.size.width = MAX(_frame.size.width, minSize.width); - } + if (maxSize.height > 0) + { + _frame.size.height = MIN(_frame.size.height, maxSize.height); + _bounds.size.height = MIN(_frame.size.height, maxSize.height); + } - if (maxSize.width > 0) - { - _frame.size.width = MIN(_frame.size.width, maxSize.width); - _bounds.size.width = MAX(_frame.size.width, minSize.width); + if (minSize.width > 0) + { + _frame.size.width = MAX(_frame.size.width, minSize.width); + _bounds.size.width = MAX(_frame.size.width, minSize.width); + } + + if (maxSize.width > 0) + { + _frame.size.width = MIN(_frame.size.width, maxSize.width); + _bounds.size.width = MAX(_frame.size.width, minSize.width); + } } } @@ -145,6 +152,42 @@ var NSDatePickerDefaultSize = 22, return self; } +// The frame adjustment is not the same if there's a calendar & a clock, or just a clock +- (CGRect)_nib2CibAdjustment +{ + // Theme has not been loaded yet. + // Get attribute value directly from the theme or from the default value of the object otherwise. + var theme = [Nib2Cib defaultTheme], + isClockOnly = (_datePickerStyle === CPClockAndCalendarDatePickerStyle) && ((_datePickerElements & CPHourMinuteSecondDatePickerElementFlag) || (_datePickerElements & CPHourMinuteDatePickerElementFlag)) && !((_datePickerElements & CPYearMonthDayDatePickerElementFlag) || (_datePickerElements & CPYearMonthDatePickerElementFlag)), + themeAttributeName = isClockOnly ? @"clock-only-nib2cib-adjustment-frame" : @"nib2cib-adjustment-frame", + frameAdjustment = [theme valueForAttributeWithName:themeAttributeName inState:[self themeState] forClass:[self class]]; + + if (frameAdjustment) + return frameAdjustment; + + if ([self hasThemeAttribute:themeAttributeName]) + { + frameAdjustment = [self currentValueForThemeAttribute:themeAttributeName]; + + if (frameAdjustment) + return frameAdjustment; + } + + return nil; +} + +- (void)_adjustNib2CibSize +{ + var frame = [self frame], + frameAdjustment = [self _nib2CibAdjustment]; + + if (frameAdjustment) + { + var finalFrame = CGRectMake(frame.origin.x + frameAdjustment.origin.x, frame.origin.y - frameAdjustment.origin.y, frame.size.width + frameAdjustment.size.width, frame.size.height + frameAdjustment.size.height); + + [self setFrame:finalFrame]; + } +} @end