Monday, 28 April 2008

Disabling the Preferences menu item (Mac only)

In Mac OS X, the Preferences menu item for each application is in the system controlled application menu i.e. the Mail menu for Mail, the Safari menu for Safari and so on. Revolution accounts for this by assuming that the last 2 items of the Edit menu are a dividing line and Preferences. It deletes these 2 items and passes any reference to the Preferences menu item back to the Edit menu. (A similar thing happens with the Quit menu item from the File menu.)


But what if your application doesn't have any preferences and you don't want the Preferences menu item to be enabled? Disabling it in the Edit menu does nothing - the menu item is still available and still sends a menuPick message to the Edit menu.

If you delete these last 2 lines from the Edit menu, Revolution will take off the next two, so you might lose the Paste & Clear menu items without affecting the Preferences item.

My solution is to rename the Edit menu to something else - I usually choose EditMenu. Then, remembering that a menu is actually a group of buttons, I set the label of this button to "Edit", so it looks right in the menubar.

If you have created a standard set of menus using the Menu Builder, you will now see the divider and the Preferences menu item back at the end of your EditMenu menu. Now you can delete them. The Preferences menu item will still appear in the application menu, but it will now be disabled.

Until next time,
Sarah

Monday, 14 April 2008

Sending messages at a specific time

This will be the final part of my current thread about sending messages. This time, I'm going to look at scheduling messages for a certain time. Say for instance that you want your application to archive a log at 3 am every day, or remind you to stop work at 5 pm, or something like that. You can use the normal message sending routines, but you have to calculate the number of seconds between now and when you want this message to arrive. Here is my script that does all this for you:


-- sendMessageAt pHandlerName, pWhere, pAtTime
--
-- e.g. sendMessageAt "showClock", "fld ID 1026 of cd 1", "11:10"
-- e.g. sendMessageAt "buildList", "cd 1", "14:45"
--
-- send message at a specified time
-- use 24 hour time, so next available will be used.
-- requires setupNextCall handler below
--
on sendMessageAt pHandlerName, pWhere, pAtTime
    -- get the current time in dateItems format
    put the seconds into timeNow
    put timeNow into eventTime
    convert eventTime to dateItems

    -- split the message time into hours & minutes
    set the itemdel to ":"
    put round(item 1 of pAtTime) into atHour
    put round(item 2 of pAtTime) into atMin

    -- see if the time is earlier than the current time & increment the day to put it into tomorrow if so
    set the itemdel to comma
    if atHour > item 4 of eventTime or (atHour = item 4 of eventTime and atMin <= item 5 of eventTime) then
        add 1 to item 3 of eventTime
    end if

    -- put the selected hour & minute into the dateItems
    put atHour into item 4 of eventTime
    put atMin into item 5 of eventTime
    put 0 into item 6 of eventTime -- adjust to even minute

    -- convert back to seconds and calculate how long in seconds until that time
    convert eventTime to seconds
    put eventTime - timeNow into theDiff

    -- use the setupNextCall handler to process this
    setUpNextCall pHandlerName, pWhere, theDiff
end sendMessageAt


-- setupNextCall pHandlerName, pWhere, pInSeconds
--
-- e.g. setupNextCall "buildList", "cd 1", 60
-- e.g. setupNextCall "preOpenStack", "me", 5
--
-- send a message in a specified number of seconds
-- clears any pending instances first
--
on setUpNextCall pHandlerName, pWhere, pInSeconds
    -- list the pendingMessages and cancel all existing instances of this message
    put the pendingmessages into pMess
    repeat for each line p in pMess
        if item 3 of p contains pHandlerName then cancel item 1 of p
    end repeat

    -- if the location of the handler cannot be found, just leave
    if there is not a pWhere then exit setupNextCall

    -- send the handler in the specified number of seconds or milliseconds
    if pInSeconds < 1 then
        do "send " & quote & pHandlerName & quote & " to " & pWhere & " in " & pInSeconds*1000 & " milliseconds"
    else
        do "send " & quote & pHandlerName & quote & " to " & pWhere & " in " & pInSeconds & " seconds"
    end if
end setUpNextCall

As you can see, this uses two handlers, but you only need to worry about the first one - it calls the second one itself.

To set this up, you need to specify 3 parameters: the name of the handler to be sent, the name of the object whose script contains that handler, and the time at which the message is to occur in HH:MM format using the 24 hour clock.

So to archive a log at 3 am, it would be called this way:
    sendMessageAt "archiveLog", "me", "03:00"
Very often, the 2nd parameter can be "me", if the object is sending a handler in it's own script. If not, you have to specify the name of the object i.e. you have to put: the name of stack "Whatever", not just stack "Whatever". e.g.
    sendMessageAt "stopWork", the name of stack "Alarms", "07:48"
The last thing is to have a way of monitoring your pendingMessages. Revolution's message box will do this for you if you click the 5th button along:

This gives you a list of pending messages. Each line shows the messages ID, the time when it will happen in long seconds format, the name of the message and the location of the handler. You can set it to auto update or just update it when you want. However I have a couple of problems with this display. Firstly, the time format is meaningless to the human eye, so you have really no idea when a particular message is going to happen. Secondly, if you have GLX2 installed, the message box usually fills up with glx2 messages any time you move the mouse.

So I wrote my own Pending Message Manager, which you can download from http://www.troz.net/Rev/plugins.php. This allows you to see the actual time of any event. If you un-check "Show Rev messages", it will hide any GLX2 messages as well as any Rev messages. Download it, un-compress the file and put it into your Plugins folder.

Until next time,
Sarah

Monday, 7 April 2008

Calculating when to send messages

In my previous post about messages, I used the example of updating a clock display. I had the display updating once per minute, but there are obvious problems with this. If I start the handler at 8:38:59 am, the clock field will show 8:38 and will continue to show 8:38 for the next 60 seconds even though the time actually changed to 8:39 one second later.

There are two ways around this: update the clock every second, or set the update to occur on the minute. Updating every second is easy - just alter the previous script so that it sends the message in 1 second instead of in 60 seconds.

However I prefer the method of working out when the update is needed and only updating then. This seems more efficient. So here is my method for doing this:


on updateClock
    -- stop the message in case there are multiples running
    cancelMessage "updateClock"

    -- update the display
    put the time into fld "Clock"

    -- store the current time in seconds
    put the seconds into tNow

    -- work out how long (in seconds) until the next minute
    put tNow into tNextUpdate
    convert tNextUpdate to dateItems
    add 1 to item 5 of tNextUpdate
    put 0 into item 6 of tNextUpdate
    convert tNextUpdate to seconds

    -- calculate the difference in seconds between the current time and the next minute
    put tNextUpdate - tNow into tSecsDiff

    -- send the update message to happen as the minutes tick over to the next
    send "updateClock" to me in tSecsDiff seconds
end updateClock


on cancelMessage pHandlerName
    put the pendingmessages into pMess
    repeat for each line p in pMess
        if item 3 of p contains pHandlerName and \
                item 4 of p contains the effective filename of this stack then
            cancel item 1 of p
        end if
    end repeat
end cancelMessage


When the update handler starts, it cancels any other instances of that message that might be in the pending messages list. Then it displays the time as before. The difference is in the next bit where it works out how long it should be until the clock is updated again. This can vary - if the clock was updated at 8:28:59, then it needs to be updated in 1 second. If it was updated at 8:28:01, then there can be 59 seconds before the next update.

This handler stores the current time in seconds format. Then, in a separate variable, it works out the time of the next minute. When a date or time is converted to dateItems, you get a comma-delimited list showing year, month, day, hour, minutes, seconds and day of the week. By adding 1 to item 5 and setting item 6 to zero, this is altered to show the next minute exactly. Then this new time is converted back to seconds so that the number of seconds between the current time and that time can be calculated. This gives the seconds until the clock needs to be updated.

Try this out. Make a stack with a field called "Clock". Put the handlers listed above into the stack scripts. In the message box, type "updateClock" and press Return. You should find that the clock updates exactly on the minute, according to your computer's system clock.

A word of warning: messages sent using the "send" command only occur if your application is not busy doing something else. So do not rely on them for exact timings. Mostly they will be right, but if you have a large stack and you start saving at 15:04:59, the clock may not be updated until 15:05:02. The message WILL happen, but only when nothing else is happening.

Ooops - mistake in previous post

Sorry people. At the end of the last post, I showed you a handler to cancel messages only from the calling stack. That handler will work in c=some circumstances, but not in all, so I have changed it. The original post now contains the amended handler for anyone who comes to it for the first time, but if any of you have already copied it, here is the new version, which I think will work in all circumstances. If you find a case where it doesn't work, please let me know.

on cancelMessage pHandlerName
put the pendingmessages into pMess
repeat for each line p in pMess
if item 3 of p contains pHandlerName and \
item 4 of p contains the effective filename of this stack then
cancel item 1 of p
end if
end repeat
end cancelMessage

Monday, 31 March 2008

Sending messages

The best way to have something happen regularly in Revolution is to use the "send" command. e.g. to have a clock display update once a minute, you could have a script like this:

on updateClock
    put the short time into fld "Clock"
    send "updateClock" to me in 60 seconds
end updateClock

To start it off, you would need to call "updateClock", then it would happen automatically every minute. The best thing is that this doesn't tie up any processing time - it just waits for the 60 seconds, then does it again.

One possible area of problems with messages is forgetting to cancel them. If you go to a different card, or close that stack and leave the message running, it may not be able to find the "Clock" field, so will cause an error. To avoid this, messages need to be cancelled to make them stop. I have a generic handler for this:
on cancelMessage pHandlerName
  put the pendingmessages into pMess
  repeat for each line p in pMess
    if item 3 of p contains pHandlerName then cancel item 1 of p
  end repeat
end cancelMessage
So when closing the stack with the clock display, I would have this line:
   cancelMessage "updateClock"
Another problem can be multiple copies of the same message building up. If for example, you sent "updateClock" in the "openStack" handler and again in the "resumeStack" handler, then it would be running twice, with each occurrence spawning another. To avoid this, I use one of two techniques:

Cancel the message at the start of the handler
    on updateClock
        cancelMessage "updateClock"
        put the short time into fld "Clock"
        send "updateClock" to me in 60 seconds
    end updateClock
or check for that message being already in the pendingMessages before re-sending
    on updateClock
        put the short time into fld "Clock"

        if the pendingmessages contains "updateClock" is false then
            send "updateClock" to me in 60 seconds
        end if
    end updateClock
The first option will give more accurate timing of the next send, but if that is not crucial, the second option may be easier.

However, this caused me a problem last week when I was incorporating one of my stacks with one created by a colleague. We had both used the same name for a handler that was being sent regularly. Since I was cancelling the message every time I called it, the message for the other stack was being cancelled too!

As far as running the message goes, there is no problem - Revolution knows which message goes to which stack or object. If I set 2 stacks running separate "updateClock" messages and then list the pendingMessages, this is what I get:
32792,1206954370.266598,updateClock,stack "Clock Demo"
36173,1206954417.881484,updateClock,stack "Other Stack"
The first item of each line is the message ID, which you need to quote when cancelling that message. The second item is the time that message will happen in long seconds format. Then we have the name of the message and the name of the object that it will be sent to.

There is no problem having messages with the same name going to different objects, but my cancelMessage handler would cancel them both. So I altered the cancelMessage handler as follows:
on cancelMessage pHandlerName
    put the pendingmessages into pMess
    repeat for each line p in pMess
        if item 3 of p contains pHandlerName and \
                item 4 of p contains the effective filename of this stack then
            cancel item 1 of p
        end if
    end repeat
end cancelMessage
Now only the messages directed at the relevant stack will be cancelled.

I have some further thoughts about sending messages, but I'll leave them for another time.

Until then....
Sarah

Yes, this blog does have a future

Thanks to the people who replied to my last, rather gloomy post. I had forgotten that people reading via RSS would not show up on my hit counter, so it will always be an underestimation. However it was good to know that there are people out there reading and appreciating. Thanks to those who suggested topics as well.

I would also like to thank Bjoernke for his kind words of encouragment and recommend his blog to you all: http://bjoernke.com/.

Expect another Revolution tip tomorrow.

Until then...
Sarah

Tuesday, 18 March 2008

Does this blog have a future?

OK, I've enjoyed setting up this blog and learning how to customise it as I wish. I can recommend Google's blogger to anyone who needs a quick & easy blog, but then allows a lot of variation if you know a bit of CSS.

However, I had hoped for a bit more interest in the blog. After 4 weeks, it has only had just over 200 visitors and only one person has made a comment.

I'm going to have a go at promoting the blog for the next week or so, but if things don't look up, I will consider myself to have learned something, and gracefully allow it to fade away.

If you have any suggestions, ideas or thoughts, please add a comment to this post.

Sarah