Has anyone written a DatePicker script using AppleScriptObj-C?

Would like to figure out how to write a Date Picker script to use the Cocoa built-in date picker.
If anyone could do it, Shane could. (Kissing butt here!)

I wrote it 6 years ago.

image

http://piyocast.com/as/archives/5571

And…I packed it into “choose date lib” AppleScript libraries with sdef.

image

http://piyocast.com/as/archives/7713

But Calendar picker is too small for latest Mac environment. So, I wrote some larger calendar pickers.

image

http://piyocast.com/as/archives/8030

1 Like

I wrote one in Swift

Piyomaru,
Well, your lib is run-only and locked so we can’t see the AppleScript.

Also it only returns a list with 3 items representing the date but no time element.

It should return a date/time

I think this is a simple subject that is perfect for you to tackle yourself.

http://piyocast.com/as/archives/5571

I tried downloading and running this script.
I get an error

“Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.”

it fails on this line

set theSize to datePicker’s fittingSize()

You have to know about execute handler in main thread.
Some of Cocoa API require to run in main thread.

Apple’s Script Editor has Control-Command-R to execute whole AppleScript to run in main thread.

Script Debugger does not have this function.
I wrote a lot of this sort of code.

Shane answered a lot of time about this.
You have to find them by yourself.

We are not your caregivers.

I Got it working

-- Created 2019-02-14 by Takaaki Naganoya
-- 2019 Piyomaru Software, parts 2025 RMF Solutions
use AppleScript version "2.5"
use scripting additions
use framework "Foundation"
use framework "AppKit"

property NSAlert : a reference to current application's NSAlert
property NSDate : a reference to current application's NSDate
property NSView : a reference to current application's NSView
property NSDatePicker : a reference to current application's NSDatePicker
property NSRunningApplication : a reference to current application's NSRunningApplication

property theResult : missing value
property returnCode : 0
property cancelled : false

on pickDate(msg)
	if msg = "" then set msg to "Pick Date"
	set paramObj to {myMessage:msg, mySubMessage:"Choose a date and time…"}
	if my NSThread's isMainThread() as boolean then
		my chooseDate:paramObj
	else
		my performSelectorOnMainThread:"chooseDate:" withObject:paramObj waitUntilDone:true
	end if
	if cancelled then return false
	return theResult
end pickDate

on chooseDate:paramObj
	local aRect, aMainMes, theView, aSubMes, datePicker, theSize
	set aRect to current application's NSMakeRect(0, 0, 280, 280)
	set aMainMes to myMessage of paramObj as text
	set aSubMes to mySubMessage of paramObj as text
	
	-- create a view
	set theView to NSView's alloc()'s initWithFrame:aRect --(current application's NSMakeRect(0, 0, 280, 280))
	set datePicker to NSDatePicker's alloc()'s initWithFrame:aRect --(current application's NSMakeRect(0, 0, 280, 280))
	--datePicker's setDatePickerStyle:(NSClockAndCalendarDatePickerStyle)
	datePicker's setDatePickerStyle:(current application's NSDatePickerStyleClockAndCalendar)
	datePicker's setDatePickerElements:((current application's NSYearMonthDayDatePickerElementFlag) + (current application's NSHourMinuteSecondDatePickerElementFlag as integer))
	--datePicker's setDatePickerElements:((NSYearMonthDayDatePickerElementFlag) + (NSHourMinuteSecondDatePickerElementFlag as integer))
	datePicker's setDateValue:(NSDate's |date|())
	
	--set theSize to datePicker's fittingSize()
	set theSize to {280, 170}
	
	theView's setFrameSize:theSize
	datePicker's setFrameSize:theSize
	
	theView's setSubviews:{datePicker}
	
	set theAlert to NSAlert's alloc()'s init()
	
	-- set up alert
	tell theAlert
		its setMessageText:aMainMes
		its setInformativeText:aSubMes
		its addButtonWithTitle:"OK"
		its addButtonWithTitle:"Cancel"
		its setAccessoryView:theView
	end tell
	
	-- show alert in modal loop
	NSRunningApplication's currentApplication()'s activateWithOptions:0
	my performSelectorOnMainThread:"doModal:" withObject:(theAlert) waitUntilDone:true
	set my cancelled to (returnCode = 1001) --error number -128
	set (my theResult) to (datePicker's dateValue()) as date
end chooseDate:

on doModal:aParam
	set (my returnCode) to aParam's runModal()
end doModal:

Save this in your Script Libraries location

Use the command

set myDate to pickDate("Please Pick a Date and Time…") of script "DatePicker"
3 Likes

Hi, I was trying to understand the logic in the script.

in handler pickDate(msg), if it is not running on the main thread, then “chooseDate” is called on MainThread by “my performSelectorOnMainThread:“chooseDate:” withObject:paramObj waitUntilDone:true”.
so is it necessary in handler “chooseDate:paramObj”, to call my performSelectorOnMainThread:“doModal:” withObject:(theAlert) waitUntilDone:true ? i thought it’s already on the main thread
is it true what I am thinking. Thank you

Detecting main thread execution is not necessary.

But I usually write both main thread execution and non-main thread execution calling code.

Non-main thread execution is for debugging.

“performSelectorOnMainThread:” does not detect error point.
We can not know about the wrong point in main threaded execution.

So, in debugging phase, I use Script Editor and run AppleScript (non-main thread call) with Command-Control-R.

We can find out the wrong point.

http://piyocast.com/as/archives/10143

–my displayAlertDialog:paramObj

This is it. I usually delete non-main threaded calling, Sometimes a trace of debugging remain.

Also note that although you can’t get rid of the standard NSAlert views, you can alter them (change fonts and colors, etc). For example, hide the icon and text fields and cover the space with the datePicker view. Add a label textField as an accessory view, and it almost looks like a custom dialog (alignments tested with Monterey, Sonoma, etc. - the iOS-ified alert layout):

use framework "Foundation"
use scripting additions

property response : missing value -- AppleScript date object - note that `performSelectorOnMainThread` doesn't return anything


on run -- testing in a script editor, the Script Menu, etc
    set optionRecord to {} -- just use defaults
    my performSelectorOnMainThread:"chooseDate:" withObject:optionRecord waitUntilDone:true
    # do whatever with the response
    if response is missing value then error number -128
    return response -- or whatever date pieces
end run

# Set response property from NSAlert result.
to chooseDate:options
    set options to (options as record) & {prompt:"Choose a date and time…", ISO:false} -- merge options with defaults
    tell current application's NSAlert's alloc()'s init()
        repeat with aView in {item 4, item 5, item 6} of its |window|'s contentView's subviews -- remove item 4 for alignment
            (aView's setHidden:true) -- hide icon imageView, message textField, and informative textField
        end repeat
        set datePicker to my makeDatePicker()
        its (|window|'s contentView's addSubview:(datePicker))
        its setMessageText:(return & return & return) -- push down the informative textField to make room for added label
        its setInformativeText:""
        repeat with button in {"OK", "Cancel"} -- just two to avoid stacking the buttons
            (its addButtonWithTitle:button)
        end repeat
        its setAccessoryView:(my (makeLabelField for (prompt of options))) -- below the (hidden) informative textField
        set reply to (its runModal())
    end tell
    if reply is (current application's NSAlertSecondButtonReturn) as integer then
        set my response to missing value
    else -- just return the entire ISO date string or AppleScript date and let the caller take it from there
        set theDate to (datePicker's dateValue()) as date
        set my response to item ((ISO of options as integer) + 1) of {theDate, theDate as «class isot» as string}
    end if
end chooseDate:

# Make and return a date picker view to cover the regular NSAlert views.
to makeDatePicker()
    tell (current application's NSDatePicker's alloc()'s initWithFrame:{{17, 95}, {277, 148}})
        its setDatePickerStyle:(current application's NSClockAndCalendarDatePickerStyle)
        its setDatePickerElements:((current application's NSYearMonthDayDatePickerElementFlag as integer) ¬
            + (current application's NSHourMinuteSecondDatePickerElementFlag as integer))
        its setDateValue:(current application's NSDate's |date|())
        return it
    end tell
end makeDatePicker

# Make and return a label text field for a relocated prompt below the NSDatePicker.
to makeLabelField for (theString as text)
    tell (current application's NSTextField's labelWithString:theString)
        its setFrame:{{0, 0}, {280, 18}} -- a single line the same width as the datePicker
        its setAlignment:(current application's NSTextAlignmentCenter)
        its setLineBreakMode:(current application's NSLineBreakByTruncatingMiddle) -- mostly so that any clipping is visible
        its setFont:(current application's NSFont's fontWithName:"Helvetica Neue Bold" |size|:12) -- or regular, whatever
        # its setBordered:true -- show a border for alignment
        return it
    end tell
end makeLabelField