Ask Reuben

How to Implement Global Actions / Global ON IDLE

How can I add the same action to every dialog?

How can I add an ON IDLE to every dialog?

Run any software and chances are there is a TopMenu option and/or Toolbar entry that is available throughout the application.  With applications on Windows, you used to be able to typically go Help->About from the TopMenu and get version details etc of the application.  If you wanted the equivalent functionality in a Genero application you would have to add the TopMenu entries for an action named about about, and then in your code add …

ON ACTION about 
    CALL show_my_about_window()

… and inside this function write code to open a modal window and display the appropriate information.  This seems simple until you realise you need to add these two lines to EVERY single dialog statement in your application to achieve your goal. By dialog statement, I mean MENU, PROMPT, INPUT, CONSTRUCT, DISPLAY ARRAY, INPUT ARRAY, and DIALOG.

Similarly if you wanted to utilise ON IDLE to have your application do something (typically exit the program and free up a license) if the user did not respond for a certain period of time, then you would need to add the ON IDLE block to EVERY single dialog statement in your application.

The question I get asked is “is there a simple way to add these ON ACTION’s and ON IDLE’s to every single dialog statement”?

The technique I came up with over 10 years ago, and it has been running fine where I first implemented it ever since, is to utilise the preprocessor.  If you have not used a preprocessor before, it is a step that can occur before code is compiled to transform your sources.

The technique is to add at the top of every .4gl file,  a line to include another file.

&include "my_include_file.4gl"

This included file will include a list of directives.  In this file add a directive like so …

&define END_INPUT ON ACTION action1 CALL my_global_action1() \
    ON ACTION action2 CALL my_global_action2() \
...
    ON IDLE 60 CALL dialog_timeout() \
END INPUT

This type of directive is a simple macro.  The effect of this is that every time the code is compiled, the pre-processor will replace the identifier END_INPUT with what follows,  in this case it adds the extra ON ACTION and ON IDLE blocks before terminating the input with the expected END INPUT syntax.  This last bit is important as you have to make sure that the transformed code is legal syntax.

You can customise with our own actions, functions, and repeat for each different dialog type. By having different directives for different dialog types, this allows you to have different functionality depending on the type of dialog.  For example in a CONSTRUCT you can add an action to launch a wizard to help the user enter QBE criteria.

You can also implement more than one for each dialog type if you want different actions available in some dialogs and not others.  For example I had an END_MENU and an END_MENU_DIALOG as there were certain actions I didn’t want available in a MENU with STYLE=”dialog”.

With Display Arrays, I also used a Function Macro in order to pass the current array to the generic function.  This used base.TypeInfo.create to pass the contents of the current array as a DomNode to the generic function  …

&define END_DISPLAY_ARRAY(p1) ON ACTION array_action1 \
    CALL my_global_array_action1(base.TypeInfo.create(p1)) \
...
END DISPLAY

Once the include file is created, you then need to a) modify every .4gl to add the &include line at the top, and then b) search and replace all instances of the legal syntax i.e. “END INPUT”, with the macro identifier i.e. “END_INPUT”.  This is something suited to a major release of your software and not something you would do in a point release.

Once you have done this, from then on it is simply a case of educating your developers to type the preprocessor macro i.e. END_INPUT  etc instead of the legal syntax i.e END INPUT, and to add to your code-reviews and automated code checking scripts tests that the macro is being used everywhere and has not been missed.  If you omit this, then when the end-user is in that dialog, the global actions / timeouts will not be active.

The example above had an ON IDLE 60 CALL dialog_timeout().  This simply meant that dialog_timeout() was called every 60 seconds of inactivity, and I had logic inside dialog_timeout() to check how many times it had been called consecutively and compare this with a system configuration variable to control how many minutes I wanted to actually wait before exiting the program.  That is the actual timeout period wasn’t hard-coded into the program but every 60 seconds we would check to see if the required number of minutes had elapsed.  This allowed us to have a longer 60 minute timeout for our main menu, and a smaller 15 minute timeout on most other programs.  It also allowed sites to configure their own timeout period.  (Note: if you are using Genero Application Server, you can also utilise AUTO_LOGOUT to achieve something similar.  There are some subtle differences, the main difference is that AUTO_LOGOUT is based on the concept of a session and not an fglrun process.  AUTO_LOGOUT will be triggered for all programs in a session when there is no activity for all programs in that session, whilst ON IDLE will be triggered for an individual fglrun process when there is no activity in that dialog for that fglrun process)

When I first implemented this technique I had over 10 of these global actions in my application implementing functionality such as …

  • Help->About menu entry.
  • Contact Us.
  • Favourites, an option from which you could quickly select and launch your favourites.
  • Debug option to output the environment,  value of global variables etc, very useful for debugging and analysis.
  • Wizards to help enter data including a wizard for use inside a CONSTRUCT to enter QBE criteria.
  • Documentation

I also used it in arrays to copy the entire array to the clipboard, and before the Find and Auto-Navigate functionality was implemented in the front-end clients I had actions to do the equivalent.

The preprocessor has other uses for transforming your sources.  In a big development environment I would suggest you have as a minimum the &include directive at the top of the file as this puts you in a great position to include macros that can transform your codebase in a single recompile.