MacroRecorder

From Apache OpenOffice Wiki
Jump to: navigation, search
What are the complaints against our recorded macros?

Besides the fact that it is incomplete (no support in Draw/Impress at all and not all UI functions in Writer or Calc are recorded properly) the most important complaint is that the macro recorder of Apache OpenOffice does not create macros that follow the APIs outlined in the Developers Guide. Instead of that it creates macros that use the so called Dispatch API that is used for the communication between UI elements and the document worked on. Simply speaking the macro recorder records user actions and puts them into calls for the Dispatch API that contain the command names of the executed actions and a list of all parameters that are gathered in the user interface and applied to the document. Currently, we only support Basic as a target language for recorded macros, but basically we could add macro recorders for other scripting languages by replacing the built-in macro recorder service without needing to change any other code.

What is the reason that our Macro Recorder uses the Dispatch Framework?

Macro Recording is an operation that tries to collect the operations a user performed in the user interface of his application. The user might use his mouse or keyboard to start a function, additionally (s)he adds some parameters (e.g. by entering data in a dialog) that the selected function needs to perform the task. The expectation of the user is that whatever a macro recorder records the result of playing this recorded macro should be exactly the same as that of the operations in the GUI that have been recorded - but without the need of any additional action (like entering parameters into dialogs).

Apache OpenOffice basically meets these expectations by recording the executed functions together with all parameters that are specified. It utilizes a feature of the Dispatch API implementation: if a Dispatch API call is executed without any additional parameters though the execution would need some the called code detects that and knows how to retrieve the missing parameters. After retrieving them they are used to execute the function in the document. If the same Dispatch API call is executed with parameters the code does not try to retrieve them and directly proceeds with the call into the document, using the parameters provided with the call. If the received parameter list is incomplete the code is free to decide whether it reports an error or uses a default value.

An example

If a user selects Edit → Insert Special Character in the menu, no parameters are passed to the C++ code inside the application, only the command name that specifies the desired function. So this code opens the „Special character“ dialog and gets a character and a font name back. These two arguments are stored in a parameter list that is handed over to the code that will insert the character into the document.

If the macro recorder is activated, the same list of parameters is also passed to the recorder, together with the name of the executed command. The macro recorder knows how to create a macro using the Dispatch API from this information. Some more sophisticated features (like splitting up one function into several steps in the macro) don't add any basically new situations, so we can neglect them here.

The generated macro uses a Dispatch API call for the selected command and the collected parameters. If the macro is executed the Basic interpreter sends the same Dispatch call to the document, but now the call contains a parameter list. The C++ code inside the application now recognizes this and so does not open the dialog and instead of that proceeds with the received argument list directly.

How it works (or not)

The majority of the recording work is done in the sfx2 and framework modules. Here resides the code that implements the Dispatch API and that we can call the “Dispatcher”. It knows all code entry points for the execution of each and every command supported by the current application and passes the execution to the one that matches the command to be executed. Here it reaches some code that directly works on the document. This code can be seen as a “glue” code between the UI and the document. To support recording it must stick to some rules:

  • always confirm successful execution or execution abortion to the Dispatcher
  • collect all used parameters in a set and make it accessible to the Dispatcher
  • support the described two ways of execution: without parameters (means: show dialogs etc.) and with parameters (means: don't show any dialogs, take the parameters and treat missing ones as an error or use defaults)
  • all internally retrieved and used parameters must have UNO types that are supported by the targeted scripting language (Basic)
  • never let any UI directly perform actions on the document, always use the „collect parameters in the UI and hand them over to the document“ approach.

As you can find out pretty fast there are a lot of functions in Apache OpenOffice where recording does not work properly, in most cases this is due to a violation of the last two rules. In Draw/Impress the situation is even worse, here even the first three rules are sticked to only rarely and a rough estimation showed that fixing that would be a huge effort. So we decided to disable macro recording for these applications completely.

There is another important restriction: in any non-trivial macro the user will select and deselect objects and/or move the cursor. While the latter in Writer is done with dispatch commands also and so can be recorded in the way described above selections and cursor movements by the mouse are done in the view code directly and not via dispatch API. So there must be explicit recording support in the code carrying out the selection that currently is missing and selections using the mouse are not recorded at all. As in Draw selection using the mouse is essential, this is another reason why macro recording in Draw currently is not possible.

Macro Recording without the Dispatch API

Advanced users have more expectations: they want the macro recorder to create macros that use the documents' API as it is described in the Developers Guide. The primary motivation for this are using the macro recorder as an API teacher and using the recorded macro as a starting point for more elaborated macros.

Here are some possible ways to create such macros:

(1) Let the “glue” code use UNO based APIs only, never call the C++ APIs of the documents directly and find a way to record arbitrary UNO calls. We had to do the following:

  • Currently nearly all “glue” code does not use any UNO calls, it calls directly into the C++ core. So this code needs to be rewritten completely.
  • Currently UNO is not involved directly into UNO API calls in C++, they are just virtual function calls without any UNO runtime code executed. So nothing „built in“ in UNO can be used for recording. But there's another reason why this is not wanted anyway:
  • At recording time of course only the „entry“ UNO calls (those directly done in the “glue” code) should be recorded, not any further UNO calls that are done by the entry calls. As the “glue” code has to be rewritten completely anyway it should be possible to take that into account from the beginning.
  • So a “UNO API call recorder” must be implemented as a service that gets the calls to be recorded passed explicitly. I could imagine doing some tricks with a special UNO reference class and an overloaded “->” operator or a “recording proxy” but that would just be syntactic sugar.

(2) Let every code that wants to be recordable implement recording on its own, only provide some general helper classes. Seems to be unclear how much effort this would be and what will be the result. Spreading the recording code throughout the whole code base seems to be a bad idea from the architectural point of view. So let's not follow this approach.

(3) If we really think about reimplementing the „glue“ code between UI and core, we could implement it as scripts itself and try to extract a „recorded“ part. This will need that all UI elements like dialogs that might be called are available through UNO APIs also, but this is basically doable. Our „DialogDiet“ CWS did some important preliminary work for this as it defined stripped-down C++ APIs for nearly all dialogs that now „only“ need to be converted to UNO APIs. Some of them need more work as they contain pointers to huge C++ objects like e.g. DrawViews etc.

Only the first approach will be able to support recording in more than one language (the third one would theoretically be able to do that if we had a „meta language“ to write the scripts). Both of them will force us to completely reimplement the “glue” code and also create a lot of new API implementations that currently are missing. As some of the “parameters” used in the internal Dispatching are currently not representable in UNO types at all (and so also not in any scripting language) we had to reimplement and redesign some document core classes also. All in all the whole process would take several years as surely we will not stop all other work on Apache OpenOffice in the meantime.

Perhaps an improved recording can be put on the plate again when all document functionality will be available through API calls so that we “only” need to rewrite the “glue” code - though my rough estimation is that even this will take too much time (maybe I find some time to count the lines of code in the glue code).

Personal tools