diff --git a/AppKit/CPDatePicker/CPDatePicker.j b/AppKit/CPDatePicker/CPDatePicker.j index fbb611a1e..c4442a7b1 100644 --- a/AppKit/CPDatePicker/CPDatePicker.j +++ b/AppKit/CPDatePicker/CPDatePicker.j @@ -206,9 +206,6 @@ CPEraDatePickerElementFlag = 0x0100; if (!_locale) _locale = [CPLocale currentLocale]; - if (!_timeZone) - _timeZone = [CPTimeZone systemTimeZone]; - _datePickerTextfield = [[_CPDatePickerTextField alloc] initWithFrame:[self bounds] withDatePicker:self]; [_datePickerTextfield setDateValue:_dateValue]; @@ -309,7 +306,14 @@ CPEraDatePickerElementFlag = 0x0100; aTimeInterval = MAX(MIN(aTimeInterval, [_maxDate timeIntervalSinceDate:aDateValue]), [_minDate timeIntervalSinceDate:aDateValue]); if ([aDateValue isEqualToDate:_dateValue] && aTimeInterval == _timeInterval) + { + if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle) + [_datePickerTextfield setDateValue:_dateValue]; + else + [_datePickerCalendar setDateValue:_dateValue]; + return; + } if (_implementedCDatePickerDelegateMethods & CPDatePicker_validateProposedDateValue_timeInterval) { @@ -509,6 +513,11 @@ CPEraDatePickerElementFlag = 0x0100; [self didChangeValueForKey:@"timeZone"]; [self setNeedsLayout]; + + if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle) + [_datePickerTextfield setDateValue:_dateValue]; + else + [_datePickerCalendar setDateValue:_dateValue]; } diff --git a/AppKit/CPDatePicker/_CPDatePickerTextField.j b/AppKit/CPDatePicker/_CPDatePickerTextField.j index 59033f99e..8dbb21eaf 100644 --- a/AppKit/CPDatePicker/_CPDatePickerTextField.j +++ b/AppKit/CPDatePicker/_CPDatePickerTextField.j @@ -66,7 +66,6 @@ var CPZeroKeyCode = 48, _CPDatePickerElementView _datePickerElementView; CPDatePicker _datePicker; CPStepper _stepper; - CPTimer _timerEdition; } @@ -101,7 +100,7 @@ var CPZeroKeyCode = 48, #pragma mark - -#pragma mark Responder methods +#pragma mark Override responder methods - (BOOL)becomeFirstResponder { @@ -116,7 +115,7 @@ var CPZeroKeyCode = 48, - (BOOL)resignFirstResponder { // End the timer of editing - [self _endTimer]; + [_currentTextField _endEditing]; // Don't forget to unbind, otherwise several steppers will increase or decrease [_currentTextField unbind:@"objectValue"]; @@ -129,6 +128,10 @@ var CPZeroKeyCode = 48, return YES; } +- (BOOL)canBecomeKeyView +{ + return NO; +} #pragma mark - #pragma mark Setter Getter methods @@ -141,7 +144,6 @@ var CPZeroKeyCode = 48, { var dateValue = [aDateValue copy]; [dateValue _dateWithTimeZone:[_datePicker timeZone]]; - [_datePickerElementView setDateValue:dateValue]; } @@ -202,7 +204,7 @@ var CPZeroKeyCode = 48, return; // End the timer of editing - [self _endTimer]; + [_currentTextField _endEditing]; // Don't forget to unbind, otherwise several steppers will increase or decrease [_currentTextField unbind:@"objectValue"]; @@ -215,7 +217,7 @@ var CPZeroKeyCode = 48, if ([_currentTextField dateType] != CPAMPMDateType) { // We update the value of the stepper dependind on the textField - [_stepper setObjectValue:parseInt([_currentTextField objectValue])]; + [_stepper setObjectValue:parseInt([_currentTextField stringValue])]; [_stepper setMaxValue:[_currentTextField maxNumber]]; [_stepper setMinValue:[_currentTextField minNumber]]; @@ -281,7 +283,7 @@ var CPZeroKeyCode = 48, if (key == CPUpArrowFunctionKey) { - [self _endTimer]; + [_currentTextField _invalidTimer]; [_stepper setDoubleValue:parseInt([_currentTextField objectValue])]; [_stepper performClickUp:self]; return YES; @@ -289,7 +291,7 @@ var CPZeroKeyCode = 48, if (key == CPDownArrowFunctionKey) { - [self _endTimer]; + [_currentTextField _invalidTimer]; [_stepper setDoubleValue:parseInt([_currentTextField objectValue])]; [_stepper performClickDown:self]; return YES; @@ -299,8 +301,10 @@ var CPZeroKeyCode = 48, { if (_currentTextField == _firstTextField && [anEvent keyCode] == CPTabKeyCode) { - if ([_datePicker previousKeyView]) - [[self window] makeFirstResponder:[_datePicker previousKeyView]]; + var previousValidKeyView = [_datePicker previousValidKeyView]; + + if (previousValidKeyView) + [[self window] makeFirstResponder:previousValidKeyView]; return YES; } @@ -311,19 +315,23 @@ var CPZeroKeyCode = 48, if (key == CPRightArrowFunctionKey || [anEvent keyCode] == CPTabKeyCode) { - if (_currentTextField == _lastTextField && [anEvent keyCode] == CPTabKeyCode) { - if ([_datePicker nextKeyView]) - [[self window] makeFirstResponder:[_datePicker nextKeyView]]; + var nextValidKeyView = [_datePicker nextValidKeyView]; + + if (nextValidKeyView) + [[self window] makeFirstResponder:nextValidKeyView]; return YES; + } - } [self _selectTextField:[_currentTextField nextTextField]]; return YES; } + [self _selectTextField:[_currentTextField nextTextField]]; + return YES; + } - if ([anEvent keyCode] == CPReturnKeyCode && _timerEdition) + if ([anEvent keyCode] == CPReturnKeyCode) { - [_timerEdition fire]; + [_currentTextField _endEditing]; return YES; } @@ -350,84 +358,7 @@ var CPZeroKeyCode = 48, return; } - if ([anEvent keyCode] != CPDeleteKeyCode && [anEvent keyCode] != CPDeleteForwardKeyCode && [anEvent keyCode] < CPZeroKeyCode || [anEvent keyCode] > CPNineKeyCode) - return; - - // Here, at the first editing we launch a timer to auto-finish the editing. There is another behavior when the user has already edited something - if (!_timerEdition) - { - _timerEdition = [CPTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(_timerKeyEvent:) userInfo:nil repeats:NO]; - - // Take care about the delete key - if ([anEvent keyCode] == CPDeleteKeyCode || [anEvent keyCode] == CPDeleteForwardKeyCode) - [_currentTextField setStringKeyValue:@""]; - else - [_currentTextField setStringKeyValue:[anEvent characters]]; - } - else - { - var newFireDate = [CPDate date], - key; - - newFireDate.setSeconds(newFireDate.getSeconds() + 2); - - [_timerEdition setFireDate:newFireDate]; - - // Take care about the delete key - if ([anEvent keyCode] == CPDeleteKeyCode || [anEvent keyCode] == CPDeleteForwardKeyCode) - key = [[_currentTextField stringValue] substringToIndex:[[_currentTextField stringValue] length] - 1]; - else - key = [CPString stringWithFormat:@"%i%i",parseInt([_currentTextField stringValue]), parseInt([anEvent characters])]; - - [_currentTextField setStringKeyValue:key]; - } -} - - -#pragma mark - -#pragma mark Timer event - -/*! End of the timer -*/ -- (void)_timerKeyEvent:(id)sender -{ - _timerEdition = nil; - - if (![[_currentTextField stringValue] isEqualToString:@" "] && ![[_currentTextField stringValue] isEqualToString:@" "]) - { - var value = [_currentTextField stringValue]; - - if ([_datePicker _isEnglishFormat] && [_currentTextField dateType] == CPHourDateType) - { - if (![_datePickerElementView _isAMHour] && value != 12) - value = parseInt(value) + 12; - - if (value == 12 && ![_datePickerElementView _isAMHour]) - value = 12; - else if (value == 12) - value = 0; - } - - [_currentTextField setObjectValue:value]; - } - else - { - [_currentTextField setObjectValue:@"0"]; - } - - -} - -/*! We force to end the timer -*/ -- (void)_endTimer -{ - if (_timerEdition) - { - [_timerEdition invalidate]; - [self _timerKeyEvent:_timerEdition]; - _timerEdition = nil; - } + [_currentTextField setValueForKeyEvent:anEvent]; } @@ -518,6 +449,7 @@ var CPZeroKeyCode = 48, [_textFieldDay setDateType:CPDayDateType]; [_textFieldDay setDatePicker:_datePicker]; [_textFieldDay setAlignment:CPRightTextAlignment]; + [_textFieldDay setDatePickerElementView:self]; [self addSubview:_textFieldDay]; _textFieldMonth = [_CPDatePickerElementTextField new]; @@ -526,6 +458,7 @@ var CPZeroKeyCode = 48, [_textFieldMonth setDateType:CPMonthDateType]; [_textFieldMonth setDatePicker:_datePicker]; [_textFieldMonth setAlignment:CPRightTextAlignment]; + [_textFieldMonth setDatePickerElementView:self]; [self addSubview:_textFieldMonth]; _textFieldYear = [_CPDatePickerElementTextField new]; @@ -534,6 +467,7 @@ var CPZeroKeyCode = 48, [_textFieldYear setDateType:CPYearDateType]; [_textFieldYear setDatePicker:_datePicker]; [_textFieldYear setAlignment:CPRightTextAlignment]; + [_textFieldYear setDatePickerElementView:self]; [self addSubview:_textFieldYear]; _textFieldHour = [_CPDatePickerElementTextField new]; @@ -542,6 +476,7 @@ var CPZeroKeyCode = 48, [_textFieldHour setDateType:CPHourDateType]; [_textFieldHour setDatePicker:_datePicker]; [_textFieldHour setAlignment:CPRightTextAlignment]; + [_textFieldHour setDatePickerElementView:self]; [self addSubview:_textFieldHour]; _textFieldMinute = [_CPDatePickerElementTextField new]; @@ -550,6 +485,7 @@ var CPZeroKeyCode = 48, [_textFieldMinute setDateType:CPMinuteDateType]; [_textFieldMinute setDatePicker:_datePicker]; [_textFieldMinute setAlignment:CPRightTextAlignment]; + [_textFieldMinute setDatePickerElementView:self]; [self addSubview:_textFieldMinute]; _textFieldSecond = [_CPDatePickerElementTextField new]; @@ -558,6 +494,7 @@ var CPZeroKeyCode = 48, [_textFieldSecond setDateType:CPSecondDateType]; [_textFieldSecond setDatePicker:_datePicker]; [_textFieldSecond setAlignment:CPRightTextAlignment]; + [_textFieldSecond setDatePickerElementView:self]; [self addSubview:_textFieldSecond]; _textFieldPMAM = [_CPDatePickerElementTextField new]; @@ -566,6 +503,7 @@ var CPZeroKeyCode = 48, [_textFieldPMAM setDateType:CPAMPMDateType]; [_textFieldPMAM setDatePicker:_datePicker]; [_textFieldPMAM setAlignment:CPRightTextAlignment]; + [_textFieldPMAM setDatePickerElementView:self]; [self addSubview:_textFieldPMAM]; _textFieldSeparatorOne = [CPTextField labelWithTitle:@"/"]; @@ -614,6 +552,13 @@ var CPZeroKeyCode = 48, [_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 @@ -641,6 +586,65 @@ var CPZeroKeyCode = 48, return [[_textFieldPMAM stringValue] isEqualToString:@"AM"]; } +- (CPDate)dateValue +{ + var date = [[_datePicker dateValue] copy]; + + [date _dateWithTimeZone:[_datePicker timeZone]]; + + if (![_textFieldDay isHidden]) + date.setDate([_textFieldDay stringValue]); + + if (![_textFieldMonth isHidden]) + date.setMonth(parseInt([_textFieldMonth stringValue]) - 1); + + if (![_textFieldYear isHidden]) + date.setFullYear([_textFieldYear stringValue]); + + if (![_textFieldSecond isHidden]) + date.setSeconds([_textFieldSecond stringValue]); + + if (![_textFieldMinute isHidden]) + date.setMinutes([_textFieldMinute stringValue]); + + if (![_textFieldHour isHidden]) + { + var hour = parseInt([_textFieldHour stringValue]), + currentHour = parseInt(date.getHours()); + + if (hour != currentHour) + { + if (([_datePicker _isEnglishFormat] || [_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 @@ -1208,14 +1212,17 @@ var CPMonthDateType = 0, */ @implementation _CPDatePickerElementTextField : CPTextField { - _CPDatePickerElementTextField _nextTextField @accessors(property=nextTextField); - _CPDatePickerElementTextField _previousTextField @accessors(property=previousTextField); + _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); + + CPTimer _timerEdition; } @@ -1325,32 +1332,120 @@ var CPMonthDateType = 0, It's called when the user is editing with the keyboard @param aStringValue a CPString */ -- (void)setStringKeyValue:(id)anObjectValue +- (void)setValueForKeyEvent:(CPEvent)anEvent { - if (_dateType == CPYearDateType) - { - if ([anObjectValue length] > 4) - return + var keyCode = [anEvent keyCode]; - while ([anObjectValue length] < 4) - anObjectValue = " " + anObjectValue; + if (keyCode != CPDeleteKeyCode && keyCode != CPDeleteForwardKeyCode && keyCode < CPZeroKeyCode || keyCode > CPNineKeyCode) + return; + + var newValue = [self stringValue], + length = [newValue length], + eventKeyValue = parseInt([anEvent characters]).toString(); + + if (keyCode == CPDeleteKeyCode || keyCode == CPDeleteForwardKeyCode) + { + [_timerEdition invalidate]; + _timerEdition = nil; + newValue = [newValue substringToIndex:(length - 1)]; } else { - if ([anObjectValue length] > 2) - return + if (!_timerEdition) + { + _timerEdition = [CPTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(_timerKeyEvent:) userInfo:nil repeats:NO]; - while ([anObjectValue length] < 2) - anObjectValue = " " + anObjectValue; + if ((_dateType == CPYearDateType && length == 4) || (_dateType != CPYearDateType && length == 2) || !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(anObjectValue) > [self _maxNumberWithMaxDate]) + if (parseInt(newValue) > [self _maxNumberWithMaxDate] || ([_datePicker _isEnglishFormat] && _dateType == CPHourDateType && parseInt(newValue) > 12)) return; - if ([_datePicker _isEnglishFormat] && _dateType == CPHourDateType && parseInt(anObjectValue) > 12) - return; + [super setObjectValue:newValue]; +} - [super setObjectValue:anObjectValue]; +/*! + End of the timer +*/ +- (void)_timerKeyEvent:(id)sender +{ + var stringValue = [self stringValue]; + + _timerEdition = nil; + + if ([stringValue length]) + { + if ([_datePicker _isEnglishFormat] && [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 _isEnglishFormat] && [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 @@ -1391,12 +1486,20 @@ var CPMonthDateType = 0, */ - (void)setObjectValue:(id)anObjectValue { - var dateValue = [[_datePicker dateValue] copy]; + 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); @@ -1405,76 +1508,81 @@ var CPMonthDateType = 0, var numberDayNextMonth = [dateNextMonth _daysInMonth]; if (numberDayNextMonth < [dateValue _daysInMonth] && dateValue.getDate() > numberDayNextMonth) - dateValue.setDate(numberDayNextMonth); + [_datePickerElementView setDayDateValue:numberDayNextMonth.toString()]; - dateValue.setMonth(parseInt(anObjectValue) - 1); + [super setObjectValue:objectValue]; break; case CPDayDateType: - dateValue.setDate(parseInt(anObjectValue)); + + if (objectValue == 0 || !lengthString) + { + [self setStringValue:dateValue.getDate().toString()]; + return; + } + + [super setObjectValue:objectValue]; break; case CPYearDateType: - dateValue.setFullYear(parseInt(anObjectValue)); + + if (objectValue == 0 || !lengthString) + { + [self setStringValue:dateValue.getFullYear().toString()]; + return; + } + + [super setObjectValue:objectValue]; break; case CPHourDateType: - dateValue.setHours(parseInt(anObjectValue)); + + if (!lengthString) + { + [self setStringValue:dateValue.getHours().toString()]; + return; + } + + [super setObjectValue:objectValue]; break; case CPSecondDateType: - dateValue.setSeconds(parseInt(anObjectValue)); + + if (!lengthString) + { + [self setStringValue:dateValue.getSeconds().toString()]; + return; + } + [super setObjectValue:objectValue]; break; case CPMinuteDateType: - dateValue.setMinutes(parseInt(anObjectValue)); + + if (!lengthString) + { + [self setStringValue:dateValue.getMinutes().toString()]; + return; + } + + [super setObjectValue:objectValue]; break; } - [_datePicker setDateValue:dateValue]; -} + var newDateValue = [_datePickerElementView dateValue], + timeZone = [_datePicker timeZone]; -/*! Return the objectValue of the textField. Needed for the binding. - This returns the objectValue relative to the dateValue -*/ -- (id)objectValue -{ - var dateValue = [[_datePicker dateValue] copy]; - - switch (_dateType) + if (timeZone) { - case CPMonthDateType: - return dateValue.getMonth() + 1; - break; + var secondsFromGMT = [timeZone secondsFromGMTForDate:newDateValue], + secondsFromGMTTimeZone = [timeZone secondsFromGMT]; - case CPDayDateType: - return dateValue.getDate(); - break; - - case CPYearDateType: - return dateValue.getFullYear(); - break; - - case CPHourDateType: - return dateValue.getHours(); - break; - - case CPSecondDateType: - return dateValue.getSeconds(); - break; - - case CPMinuteDateType: - return dateValue.getMinutes(); - break; - - default: - return [super objectValue]; - break; + newDateValue.setSeconds(newDateValue.getSeconds() + secondsFromGMT - secondsFromGMTTimeZone); } - return [super objectValue]; + [_datePicker setDateValue:newDateValue]; } + #pragma mark - #pragma mark Mouse event @@ -1507,4 +1615,65 @@ var CPMonthDateType = 0, [self unsetThemeState:CPThemeStateSelected]; } + +#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; +} + @end diff --git a/Foundation/CPDateFormatter.j b/Foundation/CPDateFormatter.j index 96c809d3e..3c237d0e9 100644 --- a/Foundation/CPDateFormatter.j +++ b/Foundation/CPDateFormatter.j @@ -1224,7 +1224,7 @@ var defaultDateFormatterBehavior = CPDateFormatterBehavior10_4, // Interpret @"" as the date 2000-01-01 00:00:00 +0000, like in Cocoa. No idea why they picked this particular date. if (!aString) return [[CPDate alloc] initWithTimeIntervalSinceReferenceDate:-31622400]; - + if (aFormat == nil) return nil; @@ -2119,6 +2119,9 @@ var CPDateFormatterDateStyleKey = @"CPDateFormatterDateStyle", */ - (void)_dateWithTimeZone:(CPTimeZone)aTimeZone { + if (!aTimeZone) + return; + self.setSeconds(self.getSeconds() - [aTimeZone secondsFromGMTForDate:self]); self.setSeconds(self.getSeconds() + [aTimeZone secondsFromGMT]); }