5
$\begingroup$

I'm trying to arrange for Mathematica to perform a certain action every time a notebook is saved or closed (with or without saving). The obvious way to do this would be through event handling. So, for example, I have the bit of code

SetOptions[$FrontEnd,
   FrontEndEventActions -> {
        {"MenuCommand", "Save"} :> DoMyThing[],
        PassEventsDown -> True
    }
];

that performs the action DoMyThing[ ] whenever a notebook is saved (via either the File menu, or Ctrl-s shortcut). This also gets triggered when a modified notebook is closed by the user and they choose to save at that point (via the "Save changes to... ?" system dialog window). This is all as I want it so far.

The thing I cannot work out is how to set up different behavior based on the user clicking the "Don't Save" or "Cancel" buttons in the "Save changes to... ?" dialog. Something like

SetOptions[$FrontEnd,
    NotebookEventActions -> {
        "WindowClose" :> DoMyThing[],
        PassEventsDown -> True
    }
];

will trigger DoMyThing[ ] as soon as the user tries to close the window, even if they proceed to select "Cancel" in the subsequent dialog window, which is NOT what I want. Is there a way to capture the user input to this system dialog and direct alternate behavior from within NotebookEventActions based on their choice? Failing that, is there another solution that will do what I want? I can't see a way, for example, even to check whether the notebook in question is still open after the user makes their choice, since my custom event actions are triggered ~before~ the "WindowClose" event is passed down to the system's lower-level event-handler (unless I can control that order of operations somehow through an option of which I am unaware). Thanks for your help!

p.s.- I suppose that I could override the default behavior and write my own window-close dialog (setting PassEventsDown -> False in that case), but that seems like a sledgehammer approach, and I'm hoping there's a simpler way.

$\endgroup$
1
$\begingroup$

As you mentioned,*EventActions are called before any lower-level stuff, so yes this means you could write your own window-close dialog, but there's another way, assuming you're not killing the kernel too. It's obviously not ideal, but you can do this via RunScheduledTask and really quite simply too.

This is what I often do for event actions when I need them to apply after the default and when either the event will be done quick or the "didn't happen action" isn't time sensitive. That isn't necessarily the case here, but it's still a somewhat workable solution.

First a little macro to build our conditional events:

conditionalEvent[
   test_, true_, false_,
   timeout_: 5, poll_: .1] :=
  (With[{now = Now},
     RunScheduledTask[
      If[test // TrueQ,
       true;
       RemoveScheduledTask@$ScheduledTask,
       If[Quantity[timeout, "Seconds"] < Now - now,
        false;
        RemoveScheduledTask@$ScheduledTask
        ]
       ],
      {poll, \[Infinity]}
      ]
     ] &);
conditionalEvent~SetAttributes~HoldAll

And then we just have to write the appropriate test. In our "WindowClose" case this could be something like NotebookInformation@nb===$Failed (note that we'll have to provide a NotebookObject as we can't depend on EvaluationNotebook[] to give us what we really want.

Then just do something like this:

With[{e = EvaluationNotebook[]},
  conditionalEvent[
   NotebookInformation@e===$Failed,
   MessageDialog["Closed!"],
   MessageDialog["Still open..."]
   ]
  ][]

And for the {"MenuCommand","Save"} call we can use "ModifiedInMemory"/.NotebookInformation@nb as our test so we have:

With[{e = EvaluationNotebook[]},
  conditionalEvent[
   ! ("ModifiedInMemory" /. NotebookInformation@e),
   MessageDialog["Saved!"],
   MessageDialog["Not saved..."]
   ]
  ][]

Then just put this all together:

With[{e=EvaluationNotebook[]},
 With[{
  closeCMD=
   conditionalEvent[
    NotebookInformation@e===$Failed,
    DoMyClosingThing[],
    TwiddleMyThumbs[]
    ],
  saveCMD=
   conditionalEvent[
    ! ("ModifiedInMemory" /. NotebookInformation@e),
    DoMySavingThing[],
    TwiddleMyUnsavedThumbs[]
    ]
  },
  SetOptions[e,
   NotebookEventActions->{
    "WindowClose":>closeCMD[],
    {"MenuCommand","Save"}:>saveCMD[],
    PassEventsDown->True
    }
  ]
]

Obviously if you can find a way to check that the notebook is locked to clicking that obviates the need for any checking of this nature. Notably this isn't possible to discover via any of the Notebook options, as far as I could see (e.g. Selectable, WindowClickSelect, Editable, etc. were unchanged by the appearance of this dialog). This might be something that you could spelunk up, as there are lots of dialogs that the front end generates that attach to the Notebook window in the same way (or at least they attach on Mac) and it might be possible to snoop out what's going on there although I highly doubt this is exposed as a Mathematica code, honestly.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.