Created
July 28, 2018 17:09
-
-
Save classilla/60594267722956bd4e7805788bf18bfb to your computer and use it in GitHub Desktop.
A variation of `doubledate.m` with synchronized time pickers and examples of limiting the time interval in a modal dialogue using undocumented methods in Mac OS X Tiger.
This file contains hidden or 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 doubletime doubletime.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 NSDoubleTimePicker; | |
@interface NSDoubleTimeDelegate : NSObject { | |
NSDoubleTimePicker *_parentAlert; | |
NSDatePicker *_source; | |
} | |
- (void)datePickerCell:(NSDatePickerCell *)aDatePickerCell | |
validateProposedDateValue:(NSDate **)proposedDateValue | |
timeInterval:(NSTimeInterval *)proposedTimeInterval; | |
- (void)setParentAlert:(NSDoubleTimePicker *)parentAlert | |
withSource:(NSDatePicker *)source; | |
@end | |
@implementation NSDoubleTimeDelegate | |
- (void)datePickerCell:(NSDatePickerCell *)aDatePickerCell | |
validateProposedDateValue:(NSDate **)proposedDateValue | |
timeInterval:(NSTimeInterval *)proposedTimeInterval | |
{ | |
NSLog(@"validate"); | |
[_parentAlert onSwitchControl:_source newDate:proposedDateValue]; | |
} | |
- (void)setParentAlert:(NSDoubleTimePicker *)parentAlert | |
withSource:(NSDatePicker *)source | |
{ | |
_parentAlert = parentAlert; | |
_source = source; | |
} | |
@end | |
////// NSDoubleTimePicker | |
////// based on NSAlertCheckbox, http://cocoadev.github.io/NSAlertCheckbox/ | |
@interface NSDoubleTimePicker : NSAlert { | |
NSDatePicker *_pickertop; | |
NSDatePicker *_pickerbottom; | |
NSDoubleTimeDelegate *_topdelegate; | |
NSDoubleTimeDelegate *_bottomdelegate; | |
} | |
- (void)dealloc; | |
- (NSDoubleTimePicker *)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)setTime:(NSDate *)date; | |
@end | |
@interface NSDoubleTimePicker(Private) | |
- (void)_ensureDatePickers; | |
- (void)_addDatePickersToAlert; | |
@end | |
@implementation NSDoubleTimePicker | |
- (void)dealloc | |
{ | |
// NSLog(@"dealloc"); | |
[_pickertop release]; | |
[_pickerbottom release]; | |
[_topdelegate release]; | |
[_bottomdelegate release]; | |
[super dealloc]; | |
} | |
- (NSDoubleTimePicker *)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 (NSDoubleTimePicker *)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)setTime:(NSDate *)date | |
{ | |
[self _ensureDatePickers]; | |
[_pickertop setDateValue:date]; | |
[_pickerbottom setDateValue:date]; | |
} | |
- (void)setMinTime:(NSDate *)date | |
{ | |
[self _ensureDatePickers]; | |
[_pickertop setMinDate:date]; | |
[_pickerbottom setMinDate:date]; | |
} | |
- (void)setMaxTime:(NSDate *)date | |
{ | |
[self _ensureDatePickers]; | |
[_pickertop setMaxDate:date]; | |
[_pickerbottom setMaxDate:date]; | |
} | |
@end | |
@implementation NSDoubleTimePicker(Private) | |
- (void)_ensureDatePickers | |
{ | |
if (!_pickertop) { | |
// NSLog(@"picker init"); | |
_pickertop = [[NSDatePicker alloc] initWithFrame:NSMakeRect(10,10,295,154)]; | |
[_pickertop setDatePickerStyle:NSClockAndCalendarDatePickerStyle]; | |
//[_pickertop setDatePickerElements:NSHourMinuteSecondDatePickerElementFlag]; | |
[_pickertop setDatePickerElements:NSHourMinuteDatePickerElementFlag]; | |
_topdelegate = [[NSDoubleTimeDelegate alloc] init]; | |
[_topdelegate setParentAlert:self withSource:_pickertop]; | |
[_pickertop setDelegate:_topdelegate]; | |
_pickerbottom = [[NSDatePicker alloc] initWithFrame:NSMakeRect(10,10,295,154)]; | |
[_pickerbottom setDatePickerStyle:NSTextFieldAndStepperDatePickerStyle]; | |
//[_pickerbottom setDatePickerElements:NSHourMinuteSecondDatePickerElementFlag]; | |
[_pickerbottom setDatePickerElements:NSHourMinuteDatePickerElementFlag]; | |
_bottomdelegate = [[NSDoubleTimeDelegate 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; | |
{ | |
NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; | |
[formatter setFormatterBehavior:NSDateFormatterBehavior10_4]; | |
[formatter setDateFormat:@"HH:mm"]; // remove :ss for step < 60 | |
NSDoubleTimePicker *alert = [NSDoubleTimePicker | |
alertWithMessageText:@"One" | |
defaultButton:@"OK" | |
alternateButton:@"Cancel" | |
otherButton:nil | |
informativeTextWithFormat:@"Blah blah"]; | |
[alert setTime:[formatter dateFromString:@"11:01"]]; | |
[alert setMinTime:[formatter dateFromString:@"07:30"]]; | |
[alert setMaxTime:[formatter dateFromString:@"17:45"]]; | |
int32_t result = [alert runModal]; | |
NSDate *date = [alert date]; | |
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