Scriptable Timer / Stopwatch

Hi McUsr,

Think it’s working now, but still need more testing and cleaning up. Also, need to reinitialize for a second run. Not sure where the best place is to do that. Still thinking about somethings.

script AppDelegate
	property parent : class "NSObject"
	
	-- IBOutlets
	property theWindow : missing value
    
    -- first step
    -- create the first notification
    property notifTitle: "Notification4"
    property notifSubtitle: "Click here to select split/pause."
    property notifInfoText: "Current mode: "
    property notifActionButtonTitle: "Start"
    property notifOtherButtonTitle: "Cancel"
    property notifSound: "Single Click"
    property splitPauseMode: "Split"
    property startDate: missing value
    property totalTime: 0
    property splitTimes: {}
    -- now every text field of the notification is filled
    -- next is boolean variables, but check out how it looks in a notification
    -- send the notification after finished launching
    
    -- step 2
    -- user selects start button if user wants split and to start timing
    -- else user selects content area to toggle split to pause

    -- see Delegate instance methods at bottom
    
	on applicationWillFinishLaunching_(aNotification)
		-- Insert code here to initialize your application before any files are opened 
	end applicationWillFinishLaunching_
    
    -- reopen handler
    on applicationShouldHandleReopen_hasVisibleWindows_(theApplication, aFlag)
        -- initialize properties
        -- not sure if I should do it here or after user presses stop button
        my applicationDidFinishLaunching_(missing value)
        return NO
    end applicationShouldHandleReopen_hasVisibleWindows_
    
    -- main
    on applicationDidFinishLaunching_(aNotification)
        -- set the delegate to script
        current application's NSUserNotificationCenter's defaultUserNotificationCenter's setDelegate_(me)
        set notifInfoTextMode to notifInfoText & splitPauseMode
        my sendNotification_(notifTitle,notifSubtitle,notifInfoTextMode,notifActionButtonTitle,notifOtherButtonTitle,notifSound)
        return
    end applciationDidFinishLaunching_

    -- method for sending a notification
    on sendNotification_(aTitle, aSubtitle, aInfoText, aActionButtonTitle, aOtherButtonTitle, aSound)
        -- make the notification
        set myNotification to current application's NSUserNotification's alloc()'s init()
        set myNotification's title to aTitle
        set myNotification's subtitle to aSubtitle
        set myNotification's informativeText to aInfoText
        set myNotification's actionButtonTitle to aActionButtonTitle
        set myNotification's otherButtonTitle to aOtherButtonTitle
        set myNotification's soundName to aSound
        -- schedule the notification
        current application's NSUserNotificationCenter's defaultUserNotificationCenter's scheduleNotification_(myNotification)
        return
    end sendNotification_
    
    -- DELEGATE INSTANCE METHODS
    
    -- Instruction to force presentation for when application process is frontmost
    on userNotificationCenter_shouldPresentNotification_(aCenter, aNotification)
        return yes
    end userNotificationCenter_shouldPresentNotification_
    
    -- delivered
    on userNotificationCenter_didDeliverNotification_(aCenter, aNotification)
        return
    end userNotificationCenter_didDeliverNotification_
    
    -- activated in content field or action button
    on userNotificationCenter_didActivateNotification_(aCenter, aNotification)
        set userActivationType to (aNotification's activationType) as integer
        if userActivationType is 1 then
            say "Contents clicked"
            my contentsClicked_(aNotification)
        else  if userActivationType is 2 then
            say "action button clicked"
            my actionButtonClicked_(aNotification)
        else
            say "none"
        end if
        return
    end userNotificationCenter_didActivateNotification_
    
    -- END DELEGATE INSTANCE METHODS
    
    -- user subroutines
    
    on contentsClicked_(aNotification)
        if notifActionButtonTitle is "Start" then
            -- toggle split/pause
            if splitPauseMode is "Split" then
                set splitPauseMode to "Pause"
            else
                set splitPauseMode to "Split"
            end if
            -- remove notification
            current application's NSUserNotificationCenter's defaultUserNotificationCenter's removeDeliveredNotification_(aNotification)
            -- send new notification
            my applicationDidFinishLaunching_(missing value)
        else if notifActionButtonTitle is "Stop"
            -- log split time
            set curDate to (current date)
            set timeDiff to curDate - startDate
            set end of splitTimes to totalTime + timeDiff
            log splitTimes
            -- set subtitle
            set notifSubtitle to "Split time: " & (totalTime + timeDiff) & " secs"
            -- split or pause
            if splitPauseMode is "Pause" then
                -- add current time to total time
                set totalTime to totalTime + timeDiff
                -- set action button title to restart
                set notifActionButtonTitle to "Restart"
            end if
            -- remove notification
            current application's NSUserNotificationCenter's defaultUserNotificationCenter's removeDeliveredNotification_(aNotification)
            -- send new notification
            my applicationDidFinishLaunching_(missing value)
        else -- action button title is "Restart"
           -- do nothing
        end if
        beep 1
        return
    end contentsClicked_
    
    on actionButtonClicked_(aNotification)
        if notifActionButtonTitle is "Start" then -- start timer
            -- change action button title
            set notifActionButtonTitle to "Stop"
            -- start or restart timer
            set startDate to (current date)
            -- change subtitle
            set notifSubtitle to "Click here to " & splitPauseMode
            -- send new notification
            my applicationDidFinishLaunching_(missing value)
        else if notifActionButtonTitle is "Stop" then
            -- calculate total time and inform user
            set curDate to (current date)
            set timeDiff to curDate - startDate
            set totalTime to totalTime + timeDiff
            log splitTimes
            log totalTime
            -- reset initial properties for new notification here
            
        else -- action button title is "Restart"
            -- reset start date
            set startDate to (current date)
            -- change action button to "Stop"
            set notifActionButtonTitle to "Stop"
            -- change subtitle
            set notifSubtitle to "Click here to " & splitPauseMode
            -- send new notification
            my applicationDidFinishLaunching_(missing value)
        end if
        beep 2
        return
    end actionButtonClicked_
    
    -- end user subroutines
	
    -- quit app if user presses close button
    on applicationShouldTerminateAfterLastWindowClosed_()
        return true
    end applicationShouldTerminateAfterLastWindowClosed_
    
	on applicationShouldTerminate_(sender)
		-- Insert code here to do any housekeeping before your application quits 
		return current application's NSTerminateNow
	end applicationShouldTerminate_
	
end script

Almost there. :slight_smile:

Edited: using the quote here to see if formatting can be saved:

Have a good day,
kel

Hello kel.

It starts to get meat on it. :slight_smile: I think it is only two placess you can reinitialize, and that is on the stop button when you have finally stopped, or on the start button, when you restart it. Now you can see the benefits, of using the model-view controller pattern, as it allows you to have most of the controller logic in one place, and not spread around the buttons, though, you mat easily get away with just programming the buttons. :slight_smile: Here is an interesting paper, that gives you some perspective on the model view controller pattern: The DCI Architecture: A New Vision of Object-Oriented Programming, should you wish to read it.

Have a nice day. :slight_smile:

Hi McUsr,

I’ve read that before from your link although I can’t remember what it was about. I’ll look through it again. What I remember was that it says something like do your calculations somewhere or something. :stuck_out_tongue: I was thinking about breaking the subroutines down, but was trying to save time. Not sure if it would matter that much.

The program seems to ok speed wise although still need to optimize it. Just wanted to get the logic down first like an algorithm. Just got back and was planning to try and see what I could do to speed it up among the other things. Probably using the text in the logic is slowing it down. Ultimately, what I wanted to do was convert to AppleScript instead of the Xcode app for learning purposes mainly.

Edited: also, taking out the 'say’s would eliminate the 1 or 2 second delays. But that’s just for the testing.

Thanks and have a nice day,
kel

Hi McUsr,

Deep down in my soul I’m thinking that the initial variables should reinitialize by themselves and thinking how to do that. Need to think about that later. Maybe there is some subroutine that sets the properties back to their initial values. I have been searching the docs and couldn’t find it.

Anyway, it seems that it is working on the first run so that’s good.

Getting there,
kel

Hello kel.

Sorry for the belated answer, I have gotten fibre with complications, and now they have removed the complications, so, now I am online again!

I really mean that you should make a script object, or an object, with handlers to start and stop the watch, and then just call those methods from your notfications, since that will provide for cleaner code: When you read the “clock object”, you have all the functionality in front of you, at once, so it is easily to grasp what it does, and what not. And then when you read your notification code, then you can really focus on the notifications, that was what I meant above I guess.

I am glad it progresses for you. :slight_smile:

Edit

You don’t need to implement the stopwatch with a separate object for the clock, but it is a great boon, to actually, just view the functionality of it as a whole during design, and test it, either, by rubberducking, or implementing it as an object, to see that those parts works as a whole.

Hi Mcusr,

I did use separate handlers to start and stop the watch in the beginning. The problem was that in the user interface the Start text for the button didn’t look right if it was coming from a pause. What I had to do was switch Start to Restart. The hard part was thinking about the changing the text in the dialog and buttons. The limited amount of buttons made it like playing a game where you have to move three pieces. You can move one to two and two to three, but you can’t move three to one all at the same time. :slight_smile: You can move only one at a time. Don’t know if you know If I’m explaining my thoughts right, but anyway still looking into it. It was fun! :smiley:

Edited: looking again at the script, I would have the hardest time trying to simplify it! :lol: Yes, it is very complicated on second thought. Who would have thought that such a simple thing would be so complicated. If someone can do it, I would love to see it. Always open to examples.

Have a good day,
kel

Hi McUsr,

The reopen handler is there mainly because I was thinking what if the user clicks the icon in the dock. Still thinking about a solution for this. The user might have accidentally clicked it also. That’s why that handler is still there. Just in case you were wondering why. That’s one of the things I was thinking about.

Edited: also, the reopen handler might be used to start a new timer. It could be a new instance. Still thinking about that for memories sake.

Have a good day,
kel

Just thought of this. If the user creates new instances of the timer, then we need to keep track of how many and limit it. It’s turning into a monster! :smiley: Only in my mind. :slight_smile: