An expansion of `datepanel.m` that displays synchronized date pickers in a modal dialogue using undocumented methods in Mac OS X Tiger.
#import <Cocoa/Cocoa.h> | |
// (C)2018 Cameron Kaiser. BSD license. | |
// gcc -o doubledate doubledate.m -framework Cocoa | |
// 10.4 does not have an accessory view method for NSAlert, but we can | |
// simulate it with these undocumented methods. | |
@interface NSAlert(WithCustomStyle) | |
- (void)prepare; | |
- (id)buildAlertStyle:(int)fp8 title:(id)fp12 formattedMsg:(id)fp16 first:(id)fp20 second:(id)fp24 third:(id)fp28 oldStyle:(BOOL)fp32; | |
- (id)buildAlertStyle:(int)fp8 title:(id)fp12 message:(id)fp16 first:(id)fp20 second:(id)fp24 third:(id)fp28 oldStyle:(BOOL)fp32 args:(char *)fp36; | |
@end | |
@class NSDoubleDatePicker; | |
@interface NSDoubleDateDelegate : NSObject { | |
NSDoubleDatePicker *_parentAlert; | |
NSDatePicker *_source; | |
} | |
- (void)datePickerCell:(NSDatePickerCell *)aDatePickerCell | |
validateProposedDateValue:(NSDate **)proposedDateValue | |
timeInterval:(NSTimeInterval *)proposedTimeInterval; | |
- (void)setParentAlert:(NSDoubleDatePicker *)parentAlert | |
withSource:(NSDatePicker *)source; | |
@end | |
@implementation NSDoubleDateDelegate | |
- (void)datePickerCell:(NSDatePickerCell *)aDatePickerCell | |
validateProposedDateValue:(NSDate **)proposedDateValue | |
timeInterval:(NSTimeInterval *)proposedTimeInterval | |
{ | |
NSLog(@"validate"); | |
[_parentAlert onSwitchControl:_source newDate:proposedDateValue]; | |
} | |
- (void)setParentAlert:(NSDoubleDatePicker *)parentAlert | |
withSource:(NSDatePicker *)source | |
{ | |
_parentAlert = parentAlert; | |
_source = source; | |
} | |
@end | |
////// NSDoubleDatePicker | |
////// based on NSAlertCheckbox, http://cocoadev.github.io/NSAlertCheckbox/ | |
@interface NSDoubleDatePicker : NSAlert { | |
NSDatePicker *_pickertop; | |
NSDatePicker *_pickerbottom; | |
NSDoubleDateDelegate *_topdelegate; | |
NSDoubleDateDelegate *_bottomdelegate; | |
} | |
- (void)dealloc; | |
- (NSDoubleDatePicker *)datePicker:(NSString *)message | |
defaultButton:(NSString *)defaultButton | |
alternateButton:(NSString *)alternateButton | |
otherButton:(NSString *)otherButton | |
informativeTextWithFormat:(NSString *)format; | |
- (void)onSwitchControl:(NSDatePicker *)which newDate:(NSDate **)newDate; | |
- (NSDate *)date; | |
- (void)setDate:(NSDate *)date; | |
@end | |
@interface NSDoubleDatePicker(Private) | |
- (void)_ensureDatePickers; | |
- (void)_addDatePickersToAlert; | |
@end | |
@implementation NSDoubleDatePicker | |
- (void)dealloc | |
{ | |
// NSLog(@"dealloc"); | |
[_pickertop release]; | |
[_pickerbottom release]; | |
[_topdelegate release]; | |
[_bottomdelegate release]; | |
[super dealloc]; | |
} | |
- (NSDoubleDatePicker *)datePicker:(NSString *)message | |
defaultButton:(NSString *)defaultButton | |
alternateButton:(NSString *)alternateButton | |
otherButton:(NSString *)otherButton | |
informativeTextWithFormat:(NSString *)format | |
{ | |
NSAlert *alert = [super alertWithMessageText:message | |
defaultButton:defaultButton | |
alternateButton:alternateButton | |
otherButton:otherButton | |
informativeTextWithFormat:format]; | |
return (NSDoubleDatePicker *)alert; | |
} | |
- (id)buildAlertStyle:(int)fp8 | |
title:(id)fp12 | |
formattedMsg:(id)fp16 | |
first:(id)fp20 | |
second:(id)fp24 | |
third:(id)fp28 | |
oldStyle:(BOOL)fp32 | |
{ | |
// NSLog(@"buildAlertStyle 1"); | |
id rv = [super buildAlertStyle:fp8 | |
title:fp12 | |
formattedMsg:fp16 | |
first:fp20 | |
second:fp24 | |
third:fp28 | |
oldStyle:fp32]; | |
[self _addDatePickersToAlert]; | |
return rv; | |
} | |
- (id)buildAlertStyle:(int)fp8 | |
title:(id)fp12 | |
message:(id)fp16 | |
first:(id)fp20 | |
second:(id)fp24 | |
third:(id)fp28 | |
oldStyle:(BOOL)fp32 | |
args:(char *)fp36 | |
{ | |
// NSLog(@"buildAlertStyle 2"); | |
id rv = [super buildAlertStyle:fp8 | |
title:fp12 | |
message:fp16 | |
first:fp20 | |
second:fp24 | |
third:fp28 | |
oldStyle:fp32 | |
args:fp36]; | |
[self _addDatePickersToAlert]; | |
return rv; | |
} | |
- (void)onSwitchControl:(NSDatePicker *)which newDate:(NSDate **)newDate | |
{ | |
// Halt the delegate on the one we're setting first. | |
if (which == _pickertop) { | |
NSLog(@"control event: top"); | |
[_pickerbottom setDelegate:nil]; | |
[_pickerbottom setDateValue:*newDate]; | |
[_pickerbottom setDelegate:_bottomdelegate]; | |
} else if (which == _pickerbottom) { | |
NSLog(@"control event: bottom"); | |
[_pickertop setDelegate:nil]; | |
[_pickertop setDateValue:*newDate]; | |
[_pickertop setDelegate:_topdelegate]; | |
} else | |
NSLog(@"wtf"); | |
} | |
- (NSDate *)date | |
{ | |
[self _ensureDatePickers]; | |
return [_pickertop dateValue]; | |
} | |
- (void)setDate:(NSDate *)date | |
{ | |
[self _ensureDatePickers]; | |
[_pickertop setDateValue:date]; | |
[_pickerbottom setDateValue:date]; | |
} | |
@end | |
@implementation NSDoubleDatePicker(Private) | |
- (void)_ensureDatePickers | |
{ | |
if (!_pickertop) { | |
// NSLog(@"picker init"); | |
_pickertop = [[NSDatePicker alloc] initWithFrame:NSMakeRect(10,10,295,154)]; | |
[_pickertop setDatePickerStyle:NSClockAndCalendarDatePickerStyle]; | |
[_pickertop setDatePickerElements:NSYearMonthDayDatePickerElementFlag]; | |
_topdelegate = [[NSDoubleDateDelegate alloc] init]; | |
[_topdelegate setParentAlert:self withSource:_pickertop]; | |
[_pickertop setDelegate:_topdelegate]; | |
_pickerbottom = [[NSDatePicker alloc] initWithFrame:NSMakeRect(10,10,295,154)]; | |
[_pickerbottom setDatePickerStyle:NSTextFieldAndStepperDatePickerStyle]; | |
[_pickerbottom setDatePickerElements:NSYearMonthDayDatePickerElementFlag]; | |
_bottomdelegate = [[NSDoubleDateDelegate alloc] init]; | |
[_bottomdelegate setParentAlert:self withSource:_pickerbottom]; | |
[_pickerbottom setDelegate:_bottomdelegate]; | |
} | |
} | |
- (void)_addDatePickersToAlert | |
{ | |
NSWindow *window = [self window]; | |
NSView *content = [window contentView]; | |
float padding = 14.0f; | |
NSArray *subviews = [content subviews]; | |
NSEnumerator *en = [subviews objectEnumerator]; | |
NSView *subview = nil; | |
NSTextField *messageText = nil; | |
int count = 0; | |
[self _ensureDatePickers]; | |
// Find the main text field. | |
while (subview = [en nextObject]) { | |
if ([subview isKindOfClass:[NSTextField class]]) { | |
if (++count == 2) | |
messageText = (NSTextField *)subview; | |
} | |
} | |
if (messageText) { | |
[content addSubview:_pickertop]; | |
[_pickertop sizeToFit]; | |
[content addSubview:_pickerbottom]; | |
[_pickerbottom sizeToFit]; | |
// Expand the alert window. | |
NSRect windowFrame = [window frame]; | |
NSRect topPickerFrame = [_pickertop frame]; | |
NSRect bottomPickerFrame = [_pickerbottom frame]; | |
windowFrame.size.height += topPickerFrame.size.height + padding + | |
bottomPickerFrame.size.height + padding; | |
[window setFrame:windowFrame display:YES]; | |
// Insert the pickers below the main text field. | |
topPickerFrame.origin.y = [messageText frame].origin.y - | |
bottomPickerFrame.size.height - padding - | |
topPickerFrame.size.height - padding; | |
topPickerFrame.origin.x = [messageText frame].origin.x; | |
bottomPickerFrame.origin.y = topPickerFrame.origin.y + | |
topPickerFrame.size.height + padding; | |
bottomPickerFrame.origin.x = topPickerFrame.origin.x; | |
[_pickertop setFrame:topPickerFrame]; | |
[_pickerbottom setFrame:bottomPickerFrame]; | |
//[window makeMainWindow]; | |
[window makeKeyWindow]; | |
//[window orderFrontRegardless]; | |
//NSLog(@"Picker installed"); | |
} else | |
NSLog(@"Couldn't find message text, did not add pickers"); | |
} | |
@end | |
////// App delegate runner | |
@interface AppDelegate : NSObject | |
- (id) init; | |
- (void) applicationWillFinishLaunching: (NSNotification*) aNotification; | |
- (void) applicationDidFinishLaunching: (NSNotification*) aNotification; | |
@end | |
@implementation AppDelegate | |
- (id) init | |
{ | |
return [super init]; | |
} | |
- (void) applicationWillFinishLaunching: (NSNotification*) aNotification | |
{ | |
// NSLog(@"WillFinish"); | |
} | |
- (void) applicationDidFinishLaunching: (NSNotification*) aNotification; | |
{ | |
NSDoubleDatePicker *alert = [NSDoubleDatePicker | |
alertWithMessageText:@"One" | |
defaultButton:@"OK" | |
alternateButton:@"Cancel" | |
otherButton:nil | |
informativeTextWithFormat:@"Blah blah"]; | |
[alert setDate:[NSDate date]]; | |
int32_t result = [alert runModal]; | |
NSDate *date = [alert date]; | |
NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; | |
[formatter setFormatterBehavior:NSDateFormatterBehavior10_4]; | |
[formatter setDateFormat:@"yyyy-MM-dd"]; | |
NSString *stringFromDate = [formatter stringFromDate:date]; | |
printf("%d %s\n", result, [stringFromDate UTF8String]); | |
[[NSApplication sharedApplication] terminate:nil]; | |
} | |
@end | |
int main(int argc, char **argv) { | |
NSAutoreleasePool *shutup = [[NSAutoreleasePool alloc] init]; | |
NSApplication *application = [NSApplication sharedApplication]; | |
AppDelegate *applicationDelegate = [[AppDelegate alloc] init]; | |
[application setDelegate:applicationDelegate]; | |
[application run]; | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment