Created
July 28, 2018 17:03
-
-
Save classilla/aaade00085a04e9051feca336c778d9d to your computer and use it in GitHub Desktop.
An expansion of `datepanel.m` that displays synchronized date pickers in a modal dialogue using undocumented methods in Mac OS X Tiger.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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