Drawing framework
Introduction
The drawing framework is a framework used by Impress and Draw for the coordinated activation and deactivation of resources like views, panes, and tool bars.
The primary goal in designing the drawing framework was to turn Impress and Draw into modular applications that can easily be extended by additional features. This should be possible dynamically through extensions, not only statically at compile time. The drawing framework is accessible to extensions that are deployed via the extension manager (menu entry Tools->ExtensionManager...). This allows an extension to provide for instance a new view and to control when to show this view and in which pane to display it. This can be done in any programming language for which there is UNO API support. The [Presenter Screen|Presenter Screen extension] is one example for this.
The design of the drawing framework addresses the following goals:
- ease of use
- flexibility
- extensibility
- abstraction from underlying frameworks
The drawing framework can be seen as a resource management system, where the resources are panes, views, tool bars, commands (slots). The configuration controller synchronizes activation and deactivation of resources. It has its name from the configuration, which describes the set of active resources, i.e. the set of visible panes, views, and tool bars. Synchronization means that when two or more resources are to be activated or deactivated at the same time then the configuration controller
- orders activations and deactivations according to dependencies between resources (e.g. when a viewand a pane are to be activated then the pane is activated before the view so that the view has access to the pane),
- tries to minimize the overall time and
- reduces visual artifacts, i.e. flickering.
The drawing framework is implemented on top of and abstracts from existing framework functionality provides mostly by the SFX2 and Framework projects.
There is a glossary for the drawing framework. The API of the drawing framework is documented here.
Current State
With the experience from the implementation of the Presenter Screen extension the first incarnation of the drawing framework (that is described by older versions of this page, see history for this or older versions) has been cleaned up and simplified into its current form.
The drawing framework is used by Impress and Draw for managing views, panes, and one or two toolbars.
Framework Design
The following sections describe the inner structure of the drawing framework in more detail. It starts with the set of supported resources, the ResourceIds that identify resources, and configurations that describe the set of active or requested resources. Last comes a description of the update process that involves the actual activation and deactivation of resources.
Resources
The drawing framework has been desinged for four types of resources:
- Panes
- Views
- Tool Bars
- Command groups
While the first three need little explanation (the little explanation is done in the API documentation) the concept of command groups is not used (yet) anywhere else. A command group is a collection of commands realized by a module that implements at least the com::sun::star::frame::XDispatch interface and implements one or more commands (that to some are known as slots).
A resource is identified by a non-empty set of URLs that describe the actual resource and its dependency on other resources. One URL is called the resource URL and defines the type of the resource. A possibly empty set of anchor URLs specifies a set of resources to which the one in question is anchored. Panes for example act as containers for views and thus are their anchors: the resource URL of the left slide sorter in the left pane is
private:resource/view/SlideSorter
. Its anchor is the left pane with the URL being
private:resource/floater/LeftImpressPane
.
Here, the first part after private:resource/ specifies the type of resource:
- pane for panes
- view for views
- toolbar for tool bars
- command for command groups
The final part of the URL defines the actual resource.
ResourceId
Each resource is identified by an object that implements the com::sun::star::drawing::framework::ResourceId service with its com::sun::star::drawing::framework::XResourceId interface. The XResourceId interface gives access to a set of URLs which specify the resource. There is one URL returned by the getResourceURL() function that defines the type of the resource. A possibly empty set of URLs returned by getAnchorURLs() specifies the anchor resource.
Each resource in a configuration has to have a unique resource id. Two resource ids may have the same resource URLs but then their anchors must not be the same. An example for this would be the slide sorter being shown in two different panes. Alternatively two resource may have the same anchor but then must not have the same resource URL. An example for this is the view tab bar and the view in the center pane. The anchor of both is the center pane.
Lets take the slide sorter as an example for this. In the left pane, the slide sorter bar is described by the ResourceId
private:resource/floater/LeftImpressPane, private:resource/view/SlideSorter
The slide sorter view in the center pane id described by the ResourceId
private:resource/pane/CenterPane, private:resource/view/SlideSorter
Panes are the typical anchor resources. They do not have an anchor, therefore getAnchorURLs() returns an empty list for them. An example for an anchor that is not a pane is the view tab bar. It is linked to the center pane. It is the anchor for its tabs that each represent one view that may by displayed in the center pane. The resource ids of the two left most tabs are for example
private:resource/pane/CenterPane, private:resource/toolbar/ViewTabBar, private:resource/view/ImpressView private:resource/pane/CenterPane, private:resource/toolbar/ViewTabBar, private:resource/view/OutlineView
The ResourceId service provides four functions for creating ResourceId objects. These are createEmpty() and create() for creating an empty resource id and one that has no anchor. The createWithAnchor() variant creates a resource whose anchor is given as ResourceId object. When the later is empty then the resulting resource could have been created with create(). Finally createWithAnchorURL takes two URLs, one for the actual resource and one for its anchor. These four variants cover the most common usages but it may be extended in the future (note drawing framework API is not yet marked as published and may be modified.)
Known Resources
A list of known resources can be found here.
Configuration
A configuration describes a set of resources. There are two important configurations, which ideally are identical:
- The current configuration describes the set of currently active resources. The term active can mean different things for different types of resources. For panes, views, and tool bars it means that they are visible. For a command group it means that that group is ready to process incoming slot calls.
- The requested configuration describes the set of resources that have been requested to be active either directly by the user or by the application in response to some user input.
Usually the two configurations differ only temporarily after a new request for the activation or deactivation of a resource has been made but not yet been processed. Eventually the current configuration is updated to reflect the requested configuration. There may, however, be circumstances, that do not allow a resource to be activated or (less likely) to be deactivated. One reason for this is that some resources are created asynchronously and become available only after a little time.
See the configuration glossary entry for examples.
It is possible obtain a copy of the requested configuration and restore it later. This allows to undo temporary changes. For example, the in-place slide show uses this feature to temporarily hide the side panes and to restore them when the show ends.
Three layer design
The drawing framework consists of controllers, factories, and application logic modules. These are located on three layers. The assignment to the different layers depends on the knowledge about different aspects of the resource management:
- Application Layer
- The application layer controls when to activate or deactivate a resource. This layer contains the application logic. For example the Impress application uses a module that is responsible for activating or deactivating the slide sorter bar depending on the view that is displayed in the center pane.
- Synchronization Layer
- The synchronization layer uses the XConfigurationController to synchronize the requests from the application layer with the resource controllers in the resource layer. It has no detailed knowledge about individual resources nor about the application logic.
- Resource Layer
- The resource factories in the resource layer know how to activate or deactivate resources. The XResourceControllers are called by the XConfigurationController to update the the current configuration so that it looks like the requested configuration.
The image illustrates the three layer design for the Impress application. The ImpressModule is created by the ImpressViewShell when the user creates a new Impress document. The ImpressModule creates the other modules in the application layer that control different aspect of the application. For example they watch for user input and switch to different views in ther center pane accordingly.
The modules in the application layer communicate with the XConfgurationController in the synchronization layer to make requests for the activation or deactivation of panes and views. The XConfigurationController modifies the RequestedConfiguration accordingly.
Eventually the CurrentConfiguration is updated. The XConfigurationController then determines the order in which to activate and deactivate resources and calls the resource factories in that order to create resources that are activated or to release resources that are deactivated.
Resource factories can implement a cache to store deactivated resources. Some resources like the slide sorter bar are activated and deactivated frequently. Caching it may speed this up.
Two phase updates
The drawing framework negotiates between modules implementing application logic that want to show or hide a resource on one side and the resource factories on the other side. The negotiation process is iterative: the activation request of a resource made by one application logic module may cause the activation request of another resource made by a second module. For example, when the user switches the view in the center pane to the outline view, then the task pane is deactivated.
The negation process is split into two phases, one for collecting a list of all the necessary changes and the second for updating the GUI to reflect these changes.
- Negotiation Phase
- The negotiation phase is triggered by the initial request, for example the switch of the view in the center pane. In this phase only the requested configuration is modified. The requested change is stored in the requested configuration and then is broadcasted. Listeners may react by making further requests for activation or deactivation of other resources. These are again stored in the requested configuration and broadcasted and may lead to even more requests. All requests are put into a queue which is processed until there are no more pending requests.
- Update Phase
- When all requests have been processed and the requested configuration describes a new consistent state of the GUI then the second, the update phase is started. In this phase only the current configuration is modified.
- That is, the requested configuration is not modified directly in this phase. The application can of course request the activation or deactivation of resources. Therefore, in the update phase a copy of the requested configuration is used which remains unchanged during the update.
- The different resource factories are asked to create or relase resources so that eventually the current configuration looks like the requested configuration. Every activation and deactivation is broadcasted like the requests for the activations and deactivations were.
It is the task of the configuration controller to order the activations and deactivations according to dependencies between affected resources and to optimize the update phase with respect to time and visual artifacts/flickering.
Extensibility
The drawing framework is designed to be extensible with regard to the set of known resources and to the application logic.
For a new resource you have to provide a factory and register it at the configuration manager. An example could be a new view or a new tool bar.
For a new piece of application logic add a listener to the configuration controller and react to activations or deactivations of relevant resources.
Implementation and Examples
The examples in the following sections show how to use the drawing framework to request and access resources and how to react to changes of the current configuration. Note that the code is C++ like pseudo code that can not be compiled as is.
FrameworkHelper
The ::sd::framework::FrameworkHelper class provides two things
- the most frequently used resource URLs so that you do not have to remember them, have trouble with resource allocation, or be able to misspell resource names, and
- some helper functions that simplify the work with the UNO based drawing framework from inside core code.
The FrameworkHelper is a multi singleton. Its Instance() method returns for a ViewShellBase object the associated FrameworkHelper object. The functions provided cover
- mapping between sd::ViewShell::ShellType and view resource URL,
- obtain view objects for their URLs or ResourceIds,
- making requests for the activation or deactivation of resources,
- locking configuration updates,
- waiting for certain events to make asynchronous requests synchronous.
One example for that is a request for the activation of the slide sorter bar:
FrameworkHelper::Instance(GetViewShellBase()).RequestView(
ResourceId::createWithAnchorURL(
aComponentContext,
FrameworkHelper::msSlideSorterURL,
FrameworkHelper::msLeftImpressPaneURL));
Drawing Framework API
The following subsections provide a short introduction of the UNO API of the drawing framework. For details please refer to the API documentation (not yet available.)
Getting Access to the Drawing Framework
Using the UNO API of the drawing framework to access active resources or to request the activation or deactivation of resources starts with the XController of an Impress or a Draw document. From this you can obtain the XControllerManager and from that the XConfigurationController. The XConfigurationController is the main access point for making requests and for accessing resources.
using ::com::sun::star::uno;
using ::com::sun::star::frame;
using ::com::sun::star::drawing::framework;
Reference<XController> xController; // This is regarded as given.
Reference<XComponentContext> xComponentContext; // This also is regarded as given.
Reference<XControllerManager> xCM (xController, UNO_QUERY_THROW);
Reference<XConfigurationController> xCC (xCM->getConfigurationController());
Requesting a Resource Activation
Once you have obtained access to the XConfigurationController, making requests to activate or deactivate resources is simple. The following code requests the activation of the slide sorter bar:
xCC->requestResourceActivation(
ResourceId::createWithAnchorURL(
xComponentContext,
FrameworkHelper::msSlideSorterURL,
FrameworkHelper::msLeftImpressPaneURL));
If you compare this to the code bove that uses the FrameworkHelper to accomplish the same thing then you see, that the FrameworkHelper only hides the connection from the ViewShellBase to the XConfigurationController. The rest of the call is basically the same.
However, the drawing framework does not provide too many automatisms on this layer (that is the task of the modules in the application layer) so the pane in which to show the slide sorter has to be requested as well.
xCC->requestResourceActivation(
ResourceId::create(
xComponentContext,
FrameworkHelper::msLeftImpressPaneURL));
Note the use of ResourceId::create() instead of ResourceId::createWithAnchorURL(). This is because a pane, in contrast to the view that was requested earlier, is not bound to any other resource.
The two requests should be executed together. An intermediate update of the current configuration is not necessary and would lead to an annoying display of an empty window before the slide sorter bar is properly shown. The probability that this happens is low but not zero. Therefore it is good practice to lock the XConfigurationController while the two requests are made. This prevents unwanted updates. You can call the lock() and unlock() methods directly but it is safer to use the ConfigurationController::Lock inner class to do that. Adding this and bringing the two requests into the logical order you get
{
::sd::framework::ConfigurationController::Lock(xCC);
xCC->requestResourceActivation(
ResourceId::create(
aComponentContext,
FrameworkHelper::msLeftImpressPaneURL));
xCC->requestResourceActivation(
ResourceId::createWithAnchorURL(
aComponentContext,
FrameworkHelper::msSlideSorterURL,
FrameworkHelper::msLeftImpressPaneURL));
}
Accessing a Resource
If you want to access an active resource directly then you have to ask the XConfigurationController for it. Suppose you are interested in accessing the window of the left pane that shows the slide sorter bar:
Reference<XPane> xPane (xCC->getResource(
ResourceId::create(
aComponentContext,
FrameworkHelper::msLeftImpressPaneURL),
UNO_QUERY);
if (xPane.is())
{
Reference<XWindow> xWindow (xPane->getWindow());
if (xWindow->is())
{
// Do something with the window.
}
}
Reacting to Modifications of the Configuration
When you want to implement application logic then you have to react to user input by requesting changes to the current configuration. User input can come in many ways. Here we cover only the indirect way of (requested) changes of the current configuration made by others.
The following code turns on the slide sorter bar when the center pane is switched to the edit view, outline view, or notes view and turns it off for all other views. The drawing framework contains a module in the application layer that does essentially the same.
class SlideSorterBarManager : public XConfigurationChangeListener
{
public:
Reference<XConfigurationController> mxCC;
void SlideSorterBarManager (const Reference<XConfigurationController>& rxCC)
: mxCC(rxCC)
{
Reference<XConfigurationControllerBroadcaster> xCCB (mxCC, UNO_QUERY);
xCCB->addConfigurationChangeListener(
this,
FramworkHelper::msResourceActivationRequestEvent,
Any());
}
virtual void notifyConfigurationChange (const ConfigurationChangeEvent& rEvent)
{
if (rEvent.Type.equals(FrameworkHelper::msResourceActivationRequestEvent))
{
if (rEvent.ResourceId.is())
&& rEvent.ResourceId->isBoundToURL(FrameworkHelper::msCenterPaneURL,DIRECT))
{
// A resource has been requested for the center pane.
::rtl::OUString sResourceURL (rEvent.ResourceId->getResourceURL());
if (sResourceURL.equals(FrameworkHelper::msImpressViewURL)
|| sResourceURL.equals(FrameworkHelper::msOutlineViewURL)
|| sResourceURL.equals(FrameworkHelper::msNotesViewURL))
{
// Show the slide sorter bar.
xCC->requestResourceActivation(
ResourceId::create(
aComponentContext,
FrameworkHelper::msLeftImpressPaneURL));
xCC->requestResourceActivation(
ResourceId::createWithAnchorURL(
aComponentContext,
FrameworkHelper::msSlideSorterURL,
FrameworkHelper::msLeftImpressPaneURL));
}
else
{
// Hide the slide sorter bar for the handout view, the slide
// sorter view, and all views that where not handled above.
mxCC->requestResourceDeactivation(
ResourceId::createWithAnchorURL(
aComponentContext,
FrameworkHelper::msSlideSorterURL,
FrameworkHelper::msLeftImpressPaneURL));
}
}
}
}
};
There are three things to note in this code:
- The first if statement in the notifyConfigurationChange() method is not really necessary because the listener is registered only for this one event type and thus will not be called for other event types.
- There is no update lock surrounding the activation requests for the slide sorter bar. This is not necessary here, because the listener is only called during the negotiation phase. During this whole phase the update is locked.
- There is only one deactivation request, the one for the view. The request for the pane is missing. This is because there might be other resources bound to the pane. However, this is one of a few places where an automatism is active: when there are no resources bound to a pane when the negotiation phase ends, then the pane is deactivated.
Providing a New Resource
To provide a new resource, say a SingleColorView that fills a window with a single color, you have to
- Provide the actual view
- Provide a factory that creates a view object for a given XPane object
- Register the factory
- Request the new view
A view has to support the css::drawing::framework::XView interface which is derived from the XResource interface. Both interfaces are extremely simple because the drawing framework does not need to know much about its resources.
The knowledge is concentrated only in the factory and of course in the implementation of the new view. Both a new resource and its factory are typically deployed together so they can use private and implementation language dependent means to communicate with each other and share information.
The factory is given a ResourceId when asked for a new resource object. For the SingleColorView the ResourceId is used to look up the corresponding XPane object from which the XWindow reference is obtained. For the standard panes the XPane object supports the XUnoTunnel interface which gives access to the VCL Window pointer, but that is not used here.
class SingleColorViewFactory : XResourceFactory
{
public:
virtual Reference<XView> createResource (
const Reference<XResourceId>& rxViewId)
{
Reference<XResource> xView;
// Get the XWindow object fort the AnchorURL of the given ResourceId.
Reference<XControllerManager> xCM (xController, UNO_QUERY_THROW);
Reference<XConfigurationController> xCC (xCM->getConfigurationController());
Reference<XPane> xPane (xCC->getResource(rxViewId->getAnchor()), UNO_QUERY);
if (xPane.is())
{
// Create the view for the XWindow object.
Reference<XWindow> xWindow (xPane->getWindow());
if (xWindow.is())
xView = new SingleColorView(rxViewId, xWindow);
}
return xView;
}
virtual void releaseView (
const Reference<XResource>& rxView)
{
// Do nothing. When the caller releases its reference to the view,
// then the view will be destroyed.
}
};
class SingleColorView : drawing::framework::XView, awt::XPaintListener
{
public:
SingleColorView(
const Reference<XResourceId>& rxViewId,
const Reference<XWindow>& rxWindow)
: mxViewId(rxViewId)
{
rxWindow->addPaintListener(this);
}
virtual ResourceId getResourceId (void)
{
return maId;
}
virtual void windowPaint (const awt::PaintEvent& rEvent)
{
// Paint the area specified by the event.
}
private:
Reference<XResourceId> mxViewId;
};
The factory can be registered in two ways.
- Dynamically by calling XViewController::addViewFactory()
- Statically by adding an entry to the configuration.
For the latter you have to provide a UNO service for the view factory, say com.sun.star.drawing.framework.SingleColorViewFactory. With this, merge the following entry into the Impress.xcu file.
<node oor:name="MultiPaneGUI">
<node oor:name="Framework">
<node oor:name="ResourceFactories">
<node oor:name="org.openoffice.drawing.framework.ExampleFactory" oor:op="replace">
<prop oor:name="ServiceName">
<value>com.sun.star.drawing.framework.SingleColorViewFactory</value>
</prop>
<node oor:name="ResourceList">
<node oor:name="R0" oor:op="replace">
<prop oor:name="URL">
<value>private:resource/view/SingleColorView</value>
</prop>
</node>
</node>
</node>
</node>
</node>
</node>
Note: It is recommended to use a name for the resource factory configuration which is likely to be unique, for instance by employing the usual "org.*" or "vnd.*" scheme. If you choose a name too generic, it might conflict with existing node names, overriding other factories, causing them to be completely unavailable. In the worst case, this will result in Draw/Impress being unusable.
All what remains to be done is to request the view. To show it in the right pane, instead of the task panel, make this call:
xCC->requestResourceActivation(
ResourceId::createWithAnchorURL(
xComponentContext,
::rtl::OUString::createFromAscii("private:resource/view/SingleColorView"),
FrameworkHelper::msRightPaneURL));