Drawing framework

From Apache OpenOffice Wiki
Revision as of 08:02, 11 May 2007 by Andre (Talk | contribs)

Jump to: navigation, search

Introduction

The drawing framework is a framework employed by the Impress and Draw applications for the synchronized activation and deactivation of resources.

The primary goal in designing the drawing framework is to turn Impress and Draw into modular applications that can easily be extended by additional features. This should be possible not only statically at compile time. The drawing framework will be 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. Extensions can be located in the sdext module.

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). Different types of resources are managed by individual resource controllers. The resource controllers are synchronized by the configuration controller. Its name comes from the configuration, which describes the set of active resources, i.e. the set of visible panes, views, and tool bars. Synchronization means that activation or deactivation of resources of different types are made in a fashion that minimizes 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.

Current State

The first part of the drawing framework is implemented in child work space components1. It covers panes and views. Tool bars and commands will be addressed soon in following child work spaces.

TODO: Add proper links to the interfaces of the API when the components1 child work space is integrated.


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 to identify resources, and configurations to describe the set of active or requested resources. This is followed by the layout of the controllers that operate on the resources. Last comes a description of the update process that starts when the application requests the activation or deactivation of a resource.

Resources

The drawing framework supports 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. One 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. An example will hopefully make this clear:

The resource URL of the left slide sorter in the left pane is
private:resource/view/SlideSorter
. The left pane is the anchor, its URL is
private:resource/floater/LeftImpressPane
.

Here, the first part after private:resource/ specifies the type of resource:

  • floater 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/floater/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/floater/CenterPane, private:resource/toolbar/ViewTabBar, private:resource/view/ImpressView
private:resource/floater/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 a couple of controllers (not of the com::sun::star::frame::XController variety), 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 controllers and 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 different parts of the drawing framework are organized in three layers

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. They watch for user input and are responsible for switching views in ther center pane and for showing or hiding the side panes and the views therein.

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 asks the resource controllers in the resource layer to activate or deactivate the resources that are controlled by them. When for instance the slide sorter bar is to be hidden then first the ViewController is asked to deactivate the SlideSorterView and then the PaneController is asked to deactivate the left pane. The resource controllers use their resource factories for the actual activation or deactivation.

Resource factories can implement a cache to store deactivated resources. For example it is quite likely that the slide sorter bar will later be activated again. This can be done faster when it is not deleted but only hidden.

Two phase updates

The drawing framework negotiates between modules implementing application logic that wants to show or hide a resource on one side and the resource providers 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 the actual negotiation of the requested changes and the other 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 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 the activation or deactivation of other resources. These are again 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 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 controllers are asked to activate or deactivate 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 where.

It is the task of the configuration controller to optimize the the update phase with respect to time and visual artifacts, flickering. It does that by acquiring the relevant locks and by calling the resource controllers in the right order.


Extensibility

The drawing framework is designed to be extensible with regard to both the set of known resources as well as the set of supported types of resources.

For a new resource you have to provide a factory and register it at the responsible resource manager. An example could be a new view or a new tool bar.

For a new type of resource you have to provide a resource controller and register it at the controller manager. An exmaple for a new type of resource could be the entries of the view tab bar.

The drawing framework uses the ModuleController to handle different modules of the core implementation and of extensions. However, the drawing framework is not in its final form with respect to extensions.


Implementation and Examples

Examples for the use of the drawing framework have their own page: the Drawing Framework Examples.

FrameworkHelper

The ::sd::framework::FrameworkHelper class provides two things

  • the most frequently used resource URLs so that you do not have to remember them or have trouble with resource allocation,
  • and some helper functions to 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. From this you can obtain the XControllerManager and from this the XConfigurationController. The XControllerManager is the hub from where you have access to all sub controllers of the drawing framework, the XConfigurationController is ususally 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<XControllerManager> xCM (xController, UNO_QUERY_THROW);
Reference<XConfigurationController> xCC (xCM->getConfigurationController());

Requesting a Resource Activation

The XConfigurationController is the center piece of the drawing framework. For the most common tasks you will not need to go deeper into the framework. The following code requests the activation of the slide sorter bar:

xCC->requestResourceActivation(
    ResourceId::createWithAnchorURL(
        aComponentContext,
        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(
        aComponentContext,
        FrameworkHelper::msLeftImpressPaneURL));

Note the use of OUString(). This is because a pane, in contrast to the view that was requested earlier, is not bound to any other resource. Therefore the AnchorURL remains empty.

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 responsible resource controller for it (this may be simplified in the future.) Suppose you are interested in accessing the window of the left pane that shows the slide sorter bar:

Reference<XPaneController> xPC (xCM->getPaneController());
Reference<XPane> xPane (xPC->getPane(
    ResourceId::create(
        aComponentContext,
        FrameworkHelper::msLeftImpressPaneURL));
if (xPane.is())
{
    Reference<XWindow> xWindow (xPane->getWindow());
    if (xWindow->is())
    {
        // Do something with the window.
    }
}

Reacting to Modifications of the Configuration

The following code turns on the slide sorter bar when the center pane shows the edit view, outline view, or notes view and turns it off when the handout view or the slide sorter view is shown. 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:

  1. 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.
  2. 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.
  3. There is only one deactivation requests, 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 not 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 paints 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 XResource interface has just one method, the XView interface is even simpler; it has no method at all.

The knowledge is concentrated only in the factory and of course in the implementation of the resource. Both the resource and its factory are typically deployed together so they can use private and implementation language dependent means to communicate 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 : XViewFactory
{
public:
    virtual Reference<XView> createView (
        const Reference<XResourceId>& rxViewId,
        const Reference<frame::XController>& rxController)
    {
        Reference<XView> xView;

        // Get the XWindow object fort the AnchorURL of the given ResourceId.
        Reference<XControllerManager> xCM (xController, UNO_QUERY_THROW);
        Reference<XPaneController> xPM (xCM->getPaneController());
        Reference<XPane> xPane (xPM->getPane(rxViewId->getAnchor()));
        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<XView>& 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.

  1. Dynamically by calling XViewController::addViewFactory()
  2. 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="F0" 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>

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(
        aComponentContext,
        ::rtl::OUString::createFromAscii("private:resource/view/SingleColorView"),
        FameworkHelper::msRightPaneURL));
Personal tools