Script question: exit handlers and how to trap the Esc key?

Next
 From:  Larry Fahnoe (FAHNOE)
9918.1 
I'm working on a script that creates leaders with coordinate values which are relative either to the world or a conditionally created CPlane. If a CPlane has been created, I'd like to be able to revert to the prior coordinate frame upon exiting the script. In other environments I'd be thinking of establishing an exit handler, but so far I've not found how to do that in MoI/Javascript. While clicking the Done and Cancel UI buttons doesn't pose much of a problem, the challenge I'm running into is that pressing the Esc key seems to throw an exception that has thus far bypassed my attempts to reliably trap it. Amusingly, adding some debugging moi.ui.alerts seems to disrupt the exception handling such that I can get the CPlane to be reset, but if I remove or turn off the debugging calls, it doesn't work; a little quantum uncertainty...

My first attempt was simple:

code:
function doCoordinates {
    if ( !WaitForDialogDone() )
        return;

    if ( moi.ui.commandUI.AdjustOrigin.value)
        moi.view.setCPlaneInteractive();

    [...]
}

var savedcf = moi.view.getCPlane();
doCoordinates();
moi.view.setCPlane( savedcf);


Pressing the Esc key would bypass restoring the CPlane on exit, so I wrapped the call to doCoordinates() in a try/catch/finally:

code:
function doCoordinates {
    if ( !WaitForDialogDone() )
        return;

    if ( moi.ui.commandUI.AdjustOrigin.value)
        moi.view.setCPlaneInteractive();

    [...]
}

var savedcf = moi.view.getCPlane();
try {
    doCoordinates();
}
catch( err) {
}
finally {
    moi.view.setCPlane( savedcf);
}


This didn't work reliably either (I suspect because there are nested functions calling the same pointpicker) so I started wrapping calls to pointpicker.waitForEvent() in try/catch but this seems excessively cumbersome. I've been using the scripts provided with MoI as a style guide and none seem to resort to this sort of effort to trap an Esc. DoCurve.js is amongst the most explicit in its handling of UI events, but even it doesn't address the Esc key. pointpicker.allowNestedCancel() doesn't seem to help either. I'm chasing my tail and clearly not understanding the MoI way of doing things!

EDITED: 6 Jul 2021 by FAHNOE

  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Cemortan_Tudor
9918.2 
event == 'cancel'
- Tudor -
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Larry Fahnoe (FAHNOE)
9918.3 In reply to 9918.2 
Hi Tudor,

I wish it was that simple!

I removed my try/catch wrappers from the calls to pointpicker.waitForEvent() and added code to check for pointpicker.event == 'cancel'. This does not work any better. Pressing Esc in the inner function (commandLeader()) results in the CPlane being reset upon exit, but pressing Esc in the outer function (doCoordinates()) causes an exit without resetting the CPlane. Next I removed the try/catch around the call to doCoordinates() shown above and this results in an Esc pressed for either function not resetting the CPlane.

Here's a simplification of the code to illustrate the structure:

code:
function coordinateLeader( pointpicker, leaders) {
    [...]

    while ( true) {

	if ( !pointpicker.waitForEvent() ) {        << Esc here results in an exit without the CPlane being reset
            return false;
        }

	if ( pointpicker.event == 'finished' ) {
	    [...]
	}

	if ( pointpicker.event == 'done' ) {
	    [...]
	    break;
	}

        if ( pointpicker.event == 'cancel' )
            return false;

        if ( pointpicker.event == 'undo' ) {
	    [...]
            return false;
        }
    }

    [...]
    return true;
}

function doCoordinates() {
    if ( !WaitForDialogDone() )
        return;

    if ( moi.ui.commandUI.AdjustOrigin.value)
        moi.view.setCPlaneInteractive();

    [...]

    var pointpicker = moi.ui.createPointPicker();
    pointpicker.allowNestedCancel();
    while ( true) {
        [...]

	if ( !pointpicker.waitForEvent() ) {        << Esc here results in an exit without the CPlane being reset
            return false;
        }

        if ( pointpicker.event == 'done' )
            return true;

        if ( pointpicker.event == 'cancel' )
            return false;

        if ( pointpicker.event == 'undo' && leaders.length > 0 ) {
            [...]
            continue;
        }

        coordinateLeader( pointpicker, leaders);
        pointpicker.reset();
        pointpicker.allowNestedCancel();
    }
}

var savedcf = moi.view.getCPlane();
doCoordinates();
moi.view.setCPlane( savedcf);


--Larry

EDITED: 6 Jul 2021 by FAHNOE

  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Cemortan_Tudor
9918.4 
if (!pointpicker.waitForEvent()) {

debug('doCoordinates() canceled: ' + pointpicker.cancelReason);
moi.view.setCPlane(savedcf);
return false;
}
- Tudor -
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Michael Gibson
9918.5 In reply to 9918.1 
Hi Larry, which version of MoI are you using?

- Michael
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Larry Fahnoe (FAHNOE)
9918.6 In reply to 9918.5 
Hi Michael,

MoI v4 beta Jan-22-2020.

--Larry
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Michael Gibson
9918.7 In reply to 9918.6 
Hi Larry, so in v4 there's a mechanism for the Esc key to interrupt scripts that could be spinning in a long lasting loop. The way it's supposed to work is if you hit escape and it has been a while since the script entered an event loop it will try to force the script to exit by throwing an exception for any function calls. But in the Jan-22 beta this mechanism gets triggered a little too easily, it's been tuned up for the next beta.

I'm not sure if that's exactly what you have run into but if it is it should behave better in the next beta.

- Michael
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Larry Fahnoe (FAHNOE)
9918.8 In reply to 9918.7 
Thanks Michael,

I'll look forward to the next beta & see how it behaves. In the meantime I've got something mostly working (try/catch wrappers around pointpicker.waitForEvent()) although I don't think it is as clean as it ought to be. I'll update this thread once I test with the next beta.

--Larry
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Larry Fahnoe (FAHNOE)
9918.9 In reply to 9918.7 
Hi Michael,

In case it is relevant, or maybe just a confirmation of your thought about the v4 Esc interrupt mechanism, here is some additional data.

My script uses a 2-stage process to create multiple leaders with coordinate values and as such it makes use of the allowNestedCancel() feature. The desired behavior around Esc/Cancel is to allow more granular cancellations rather than an immediate complete cancellation of the command. The UI Cancel button is working as desired and allows the granular cancellations but the Esc key does not. When the Esc key is pressed in the inner function it triggers a cascade of cancelbutton/cancel events in the same fashion as without allowNestedCancel() & thus an immediate complete cancellation of the command.

It is interesting that without allowNestedCancel() the Esc key throws an exception but with allowNestedCancel() it does not, but in either case the Esc triggers the cascade of cancelbutton/cancel events.

I added some logging with timing data to better understand what was happening and the following log snippets attempt to illustrate the difference between the Cancel button and Esc key. The >> indicates what I did with the UI

code:
     With pointpicker.allowNestedCancel()

     Test using Cancel button (requires 3 clicks on Cancel to exit
     command, desired behavior)
     >> Click on object
        6465ms: doCoordinates() event: finished
     >> Draw out leader, wait, click on Cancel
     >> Leader disappears, Pick leader arrow tip is shown
        10149ms: trapEsc() canceled: cancelbutton
        10149ms: coordinateLeader() event: cancel
     >> Wait, click on Cancel
     >> Pick next leader point is shown
        16373ms: trapEsc() canceled: cancelbutton
        16373ms: doCoordinates() event: cancel
     >> Wait, click on Cancel
        23072ms: trapEsc() canceled: cancelbutton
        23072ms: coordinateLeader() event: cancel

     Test using Escape key (first Escape causes command to exit)
     >> Click on object
        3465ms: doCoordinates() event: finished
     >> Draw out leader, then press Esc
        5876ms: trapEsc() canceled: escape
        5876ms: coordinateLeader() event: cancel
        5911ms: trapEsc() canceled: cancelbutton
        5911ms: doCoordinates() event: cancel
        5943ms: trapEsc() canceled: cancelbutton
        5943ms: coordinateLeader() event: cancel

     Without pointpicker.allowNestedCancel()

     Test using Escape key (first Escape causes command to exit)
     >> Click on object
        4989ms: doCoordinates() event: finished
     >> Draw out leader, then press Esc
        8470ms: trapEsc() caught
        8470ms: coordinateLeader() event: cancel
        8505ms: trapEsc() canceled: cancelbutton
        8505ms: doCoordinates() event: cancel
        8541ms: trapEsc() canceled: cancelbutton
        8541ms: coordinateLeader() event: cancel


Code attached if helpful, otherwise I'll continue to look forward to the next beta.

--Larry

EDITED: 6 Jul 2021 by FAHNOE

  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Larry Fahnoe (FAHNOE)
9918.10 In reply to 9918.9 
Hi Michael,

The changes made in the Sept 10 beta definitely help in that pressing Esc no longer throws an exception.

> Update Esc script cancelling
> Use different method to determine time since last wait for event. The previous method of watching calls to
> WaitableObject::waitForEvent() is not good because there are other kinds of event loops that do not go through
> WaitableObject, like modal dialogs or combo box dropdowns. The new method should handle all types of event loops.

I am seeing something puzzling though: pressing Esc acts differently than clicking on Cancel. I tend to use Esc rather than Cancel and would like Esc to behave the same as Cancel. Admittedly I do not have a good handle on the event structure being used, but I'm attempting to write a script which behaves consistently with the rest of the MoI. I believe this was happening with the last beta, but I was hung up on the exceptions being thrown.

My Coordinates script has two pointpicker loops (or phases of operation): first, picking the leader arrow tips in doCoordinates(), and second, picking the leader points in coordinateLeader(). Although it is two functions, it is in effect a nested loop and makes use of pointpicker.allowNestedCancel() to allow more granular control over removing just the current objects rather than all objects created during the command execution.

If I click on the Cancel button in the inner phase, that phase cleans up and goes back to the outer phase which allows another leader arrow to be picked. If I press Esc instead, the inner phase cleans up, returns to the outer phase, but another cancel event is magically generated causing the command to exit. The following traces illustrate the difference & the Prompt/Action pairs explain how to reproduce the behavior.

Clicking on the Cancel button during the "Pick next leader point" (inner) phase:
code:
/Users/michael/src/moix/moi_lib/Init.cpp 310: Moi starting up
MoI version: 4.0 Beta Sep-10-2020
1ms: doCoordinates()
Prompt: “Coordinates options"
Action: Click Done
Prompt: “Pick leader arrow tip"
Action: Pick point
3444ms: doCoordinates() event: finished
3444ms: coordinateLeader()
Prompt: “Pick next leader point"
Action: Click on Cancel button
6930ms: coordinateLeader() canceled: cancelbutton
6930ms: coordinateLeader() event: cancel
Prompt: “Pick leader arrow tip”
Action: Click on Done button
8433ms: doCoordinates() event: done


Pressing Esc key during the "Pick next leader point" (inner) phase:
code:
/Users/michael/src/moix/moi_lib/Init.cpp 310: Moi starting up
MoI version: 4.0 Beta Sep-10-2020
1ms: doCoordinates()
Prompt: “Coordinates options"
Action: Click Done
Prompt: “Pick leader arrow tip"
Action: Pick point
3194ms: doCoordinates() event: finished
3195ms: coordinateLeader()
Prompt: “Pick next leader point"
Action: Press Esc key
9764ms: coordinateLeader() canceled: escape
9764ms: coordinateLeader() event: cancel
9806ms: doCoordinates() canceled: cancelbutton     <<< extra event is being magically generated
9806ms: doCoordinates() event: cancel


Code is attached for reference.

--Larry

EDITED: 6 Jul 2021 by FAHNOE

  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Michael Gibson
9918.11 In reply to 9918.10 
Hi Larry, so the escape key handling goes like this:

1. If a dialog or flyout menu window has focus, it will close that window. If it was a modal window it will give the cancel return code value.

2. If there was no dialog to target, then if there is any active selection filter it will clear that.

3. If there was no selection filter, then if a command is running it will cancel that command.

4. If there was no command running, then if there is an object using an event loop waiting for events it will cancel that making it exit the waiting state.

5. If there was no waiting object, then if there are any selected objects it will clear the selection.

6. If there was not any object selection then it looks if any objects had edit points turned on and if so then turns them off.



So the #3 step of canceling the current running command will cause any further wait calls to return as canceled until the command has exited.

That is indeed different than the Cancel button handling, a cancel button press will only either exit the outermost waitable object if it has allowNestedCancel() set, or otherwise do a full command cancel. It does not do any of those other things that the Esc key handler does.

I will take a look at skipping step #3 in the Esc key processing if there is currently a waiter with allowNestedCancel() set on it.

- Michael
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
 From:  Larry Fahnoe (FAHNOE)
9918.12 In reply to 9918.11 
Hi Michael,

Thank you both for the detailed explanation and the time it took to offer it. I took my understanding of the function of the Enter and Esc keys from the Shortcut Keys section of the MoI Command Reference. Clearly there is much more going on "under the hood" than meets the eye!!

Your thought about conditionally skipping #3 sounds plausible & I will look forward to your investigation and decision about any changes to the Esc key processing.

--Larry
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged
 

Reply to All Reply to All