Tuesday, December 12, 2017

#1 2009-09-14 06:58:43 pm

HippoMan
Member
Registered: 2008-09-18
Posts: 114

Creating an idle handler within ASOC.

Based on what I discovered and reported in my other recent thread (http://macscripter.net/viewtopic.php?id=30478), I can now write an idle handler for an ASOC application.

Here's how to do it ...

First of all, I create an IdleHandler class. Here's its interface:

#import <Cocoa/Cocoa.h>

@interface IdleHandler : NSObject {
    SEL            sel;
    NSObject*      object;
    NSTimeInterval delay;
}
+(IdleHandler*) initWithObject:(NSObject*)xobject withHandler:(SEL)xsel repeatEvery:(NSTimeInterval)xdelay;
@end


And here's its implementation:

#import "IdleHandler.h"

@interface IdleHandler()
-(IdleHandler*) internalInitWithObject:(NSObject*)xobject withHandler:(SEL)xsel repeatEvery:(NSTimeInterval)xdelay;
-(void) internalIdle;
@end

@implementation IdleHandler
-(void)internalIdle {
    NSNumber* result = (NSNumber*) [object performSelector:sel];
    @try {
        // If the return value is parseable as a time interval and it's
        // not negative, use it as the delay from now on.
        NSTimeInterval newDelay = (NSTimeInterval)[result doubleValue];
        if (newDelay >= 0.0) {
            delay = newDelay;
        }
    }
    @catch (...) {
        // do nothing
    }
    [self performSelector:@selector(internalIdle) withObject:nil afterDelay:delay];
}
-(IdleHandler*) internalInitWithObject:(NSObject*)xobject withHandler:(SEL)xsel repeatEvery:(NSTimeInterval)xdelay {
    self = [super init];
    if (self != nil) {
        object = xobject;
        sel    = xsel;
        delay  = xdelay;
        [self internalIdle];
    }
    return self;
}
+(IdleHandler*) initWithObject:(NSObject*)xobject withHandler:(SEL)xsel repeatEvery:(NSTimeInterval)xdelay {
    IdleHandler* h = [[IdleHandler alloc] internalInitWithObject:xobject withHandler:xsel repeatEvery:xdelay];
    return (h);
}
@end


Then, I just do this in my AppleScript:

Applescript:

script IdleExampleAppDelegate
   
   property idleHandler : class "IdleHandler" of current application
   
   -- inheritance
   property parent : class "NSObject"
   
   on applicationWillFinishLaunching_(aNotification)
       idleHandler's initWithObject_withHandler_repeatEvery_(me, "localIdle", 1)
   end applicationWillFinishLaunching_
   
   on applicationShouldTerminate_(sender)
       -- Insert code here to do any housekeeping before your application quits
       return my NSTerminateNow
   end applicationShouldTerminate_
   
   on idle
       display dialog "in idle handler"
       return 1
   end idle

   on localIdle()
       -- just invoke the standard idle handler
       return idle
   end localIdle
   
end script

This indeed calls my idle handler with a granularity of one second.

Note that you have to pass the me variable into the IdleHandler's initializer in order for these AppleScript handlers to be callable via Objective C.

Also note that the standard AppleScript idle handler is a special method which can't be called in this manner. However, it's simple to create a wrapper method to call it, like I did with localIdle().

Another noteworthy aspect of this is that I wrote it so that the return value of the idle handler determines the interval to wait for its next firing. This mirrors the AppleScript on idle handling.

I'm wondering if anyone sees any pitfalls to this overall approach. Are there better ways to manage the idle wait? Are there better ways to invoke the AppleScript method from within the Objective C world?

Last edited by HippoMan (2009-09-15 10:51:23 am)


Filed under: idle-handler

Offline

 

#2 2009-09-15 11:14:19 am

Craig Williams
Administrator
From:: Ft. Smith, AR
Registered: 2006-12-07
Posts: 888

Re: Creating an idle handler within ASOC.

Have you looked at NSTimer?

Offline

 

#3 2009-09-15 06:57:56 pm

HippoMan
Member
Registered: 2008-09-18
Posts: 114

Re: Creating an idle handler within ASOC.

Craig Williams wrote:

Have you looked at NSTimer?


Yes, I did, thanks. Do you know if using it has any advantage over the use of performSelector:withObject:afterDelay:?

Since I have to call performSelector: anyway in order to invoke my AppleScript handler from within the Objective C world, I thought that using the afterDelay: variation would be the easiest way to implement the idle handler. But if there is some disadvantage to using performSelector:withObject:afterDelay:, then I'll be happy to redo this with a thread, a periodic notification, and an NSTimer -- which I already know how to do.

I'm just new to all of this, and I'm still learning the subtleties of these various mechanisms.

Offline

 

#4 2009-09-15 08:40:43 pm

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5199

Re: Creating an idle handler within ASOC.

I suspect you if you use NSTimer, you won't have to use your Objective-C class.


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/

Offline

 

#5 2009-09-15 08:54:56 pm

HippoMan
Member
Registered: 2008-09-18
Posts: 114

Re: Creating an idle handler within ASOC.

Shane Stanley wrote:

I suspect you if you use NSTimer, you won't have to use your Objective-C class.


Ah ... I could invoke it right from the AppleScript.

Hmm ... I might even be able to use use performSelector:withObject:afterDelay: directly from the AppleScript, as well.

Time to do more investigation on both of these options.

Thanks for the tip!

Offline

 

#6 2009-09-15 09:22:34 pm

HippoMan
Member
Registered: 2008-09-18
Posts: 114

Re: Creating an idle handler within ASOC.

HippoMan wrote:

Hmm ... I might even be able to use use performSelector:withObject:afterDelay: directly from the AppleScript, as well.


... and it turns out that I can indeed do that. It makes the solution to this problem trivial:

Applescript:

script IdleTestAppDelegate
   
   -- inheritance
   property parent : class "NSObject"
   
   on applicationWillFinishLaunching_(aNotification)
       -- All we need is to make the following call and to write
       -- a simple idleWrapper() handler (see below). The third
       -- argument of 0.0 makes sure that "on idle" initially
       -- gets called as soon as there is any idle time, which is
       -- exactly how the standard "on idle" handler works. After
       -- that, the return value of "on idle" determines the
       -- calling frequency.
       my performSelector_withObject_afterDelay_("idleWrapper", missing value, 0.0)
   end applicationWillFinishLaunching_
   
   on applicationShouldTerminate_(sender)
       return my NSTerminateNow
   end applicationShouldTerminate_
   
   on idleWrapper()
       -- Use the return value from "on idle" to
       -- schedule its next invocation.
       set idleTime to idle
       -- Error checking goes here. Make sure that the
       -- new idleTime value is reasonable. This is
       -- left as an exercise for the reader. :)
       my performSelector_withObject_afterDelay_("idleWrapper", missing value, idleTime)
   end idleWrapper
   
   on idle
       log ("on idle")
       return 1.0
   end idle
   
end script

Note the one-liner that I put into applicationWillFinishLaunching_, and note the idleWrapper() handler. Nothing more is needed.

I tested this, and it works fine.

The reason I need the idleWrapper() handler is because passing "idle" as the first argument to performSelector_withObject_afterDelay_ results in this error:

unrecognized selector sent to object <IdleTestAppDelegate @0x2004cc040: OSAID(4)>


But no big deal.

When I get some time, I'll look into doing this with NSTimer ... but I'm sure it will be more complicated.

Anyway, thanks again for your suggestion about using NSTimer within the AppleScript. It got me thinking of this easy solution.

Last edited by HippoMan (2009-09-15 09:54:07 pm)

Offline

 

#7 2009-09-15 09:38:10 pm

HippoMan
Member
Registered: 2008-09-18
Posts: 114

Re: Creating an idle handler within ASOC.

Given that this turns out to be so simple, it begs the question of why Apple didn't build something like this into ASOC so that we could just run a standard AppleScript on idle handler.

I can see why it might have been complicated to do so under the old AppleScript Studio (on idle didn't work there, either, by the way), but a good programmer at Apple could probably add this capability to the ASOC framework in less than 30 minutes.

This makes me wonder if perhaps there are some potentially ugly and evil side effects from having this kind of idle handler in ASOC in the first place.

Any thoughts?

Last edited by HippoMan (2009-09-15 09:40:37 pm)

Offline

 

#8 2009-09-15 10:35:08 pm

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5199

Re: Creating an idle handler within ASOC.

I can't see that it will cause any problems -- performSelector_withObject_afterDelay_ is needed in some cases anyhow, such as in the sample I posted at <http://www.scriptingmatters.com/ASObjC>.

But using a timer is simple enough:

Applescript:

property timerClass : class "NSTimer"

[...]

Applescript:

on applicationWillFinishLaunching_(aNotification)
       -- Insert code here to initialize your application before any files are opened
timerClass's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(5, me, "timerDidFire:", missing value, true)
end applicationWillFinishLaunching_

   on timerDidFire_(theTimer)
       -- do stuff
       set oldInterval to theTimer's timeInterval()
       display dialog "Enter a new interval:" default answer (oldInterval as text)
       set newInterval to (text returned of result) as number
       if oldInterval ≠ newInterval then
           -- to change interval, invalidate timer and make new one:
           theTimer's invalidate()
           timerClass's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(newInterval, me, "timerDidFire:", missing value, true)
       end if
   end timerDidFire_


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/

Offline

 

#9 2009-09-15 11:25:24 pm

HippoMan
Member
Registered: 2008-09-18
Posts: 114

Re: Creating an idle handler within ASOC.

Yes, now I see that it isn't too complicated to use an NSTimer.

So now we know two ways of doing this in ASOC. Again it makes me wonder why Apple didn't pick one of them (or perhaps some other approach) and just use it to enable the on idle functionality in ASOC.

Oh well, maybe we'll never know. In any case, I now understand how to do this, so all's well that ends well.

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)