Hi Frank,

sorry for the delay on this (copy and paste from the apology I'm sending
to everyone: the end of the first academic semester [it ended last
Friday here in Argentina - followed by a 2 weeks winter holidays]
kept me too busy; to this add a Patagonian trip to see the glacier
tearing apart [same as http://es.youtube.com/watch?v=TZJYN8qnirE]
and a 22 nights Krystov Kieslowski film retrospective;
all this together kept me away from hobby-things like playing with OOo API).

I just started testing the css.sdb.application.DefaultViewController,
but as the first try reveals two important issues, I decided to report
my findings before finishing a nicer/cleaner demo.
"Important issue" means that the first two features I tested up to
know (the context menu interceptor and the css.view.XSelectionSuplier)
can not be used: the first not work and the later crashes OOo.

The demo can be found in
http://www.ArielConstenlaHaile.com.ar/ooo/tmp/dbaccess/DatabaseControllerDemo.zip
it's a NetBeans project, oxt inside the dist folder; to debug
the NBP you may need to change the settings (I can't recall what I've
done to make it work with the 3-layer OOo), or you can simply install
the extension (it is ridiculously verbose, so if you run OOo from the
command line will get info about what's going on).
The extension is copy&paste from other working code, but does nothing
useful, anyway it may help to test the features [well.. the "Edit
hyperlink" entry on Writer has been very useful for me!]. Check the
extension in a Writer document context menu and then in a Base
one to see the differences.

**********************************************************************
**********************************************************************

Context menu interceptor

In case you are too busy for this, take a look at the two pictures that
show the issue:
http://www.ArielConstenlaHaile.com.ar/ooo/tmp/dbaccess/no_seps_no_menu_item_image.png
http://www.ArielConstenlaHaile.com.ar/ooo/tmp/dbaccess/with_seps_and_menu_item_image.png

just by looking at them you'll see that on the context menu:

1. separators are converted to common menu items
2. menu item images are removed
3. although not visible [and AFAIK useless in context menus],
   the help command is not preserved
4. [and if you select an entry added by the extension] the
   menu item command is never executed

Ad 1.: in DBTreeListBox::CreateContextMenu [1], if the PopupMenu is
modified by a context menu interceptor, you also modify the menu item
IDs in lcl_adjustMenuItemIDs with a new ID generated for its command.
Inside the <code>for</code> loop in lcl_adjustMenuItemIDs you don't
check if the menu item is a seperator, so it ends up converting
separators in common menu items. Here adding the following code solves
the problem:

    void lcl_adjustMenuItemIDs( Menu& _rMenu, IController&
_rCommandController )
    {
        USHORT nCount = _rMenu.GetItemCount();
            for ( USHORT pos = 0; pos < nCount; ++pos )
            {
            // do NOT adjust SEPARATORS!
            MenuItemType nItemType = _rMenu.GetItemType( pos );
            if ( nItemType == MENUITEM_SEPARATOR )
                continue;

            //...
            }
    }

Ad 2.: commands added by context menu interceptors may have an image
[never tested setting the property "Image" in the ActionTrigger - at
least getting it has issues], lcl_adjustMenuItemIDs ignores that. The
following keeps the menu item images:

            Image aImage = _rMenu.GetItemImage( nId );
            if ( !!aImage )
                _rMenu.SetItemImage( nCommandId, aImage );


Ad 3.: the ActionTrigger has also a "HelpURL" property, that may also be
conserved by a _rMenu.GetHelpCommand and _rMenu.SetHelpCommand...
although I have no idea if the new extensible help feature will work if
the client code sets a custom help command here: F1 when a mouse is over
a context menu item does nothing, no matter if the command is built-in.
Anyway the following conserves the help command url:

            ::rtl::OUString aHelpURL = _rMenu.GetHelpCommand( nId );
            if ( aHelpURL.getLength() )
                _rMenu.SetHelpCommand(  nCommandId, aHelpURL  );



So that 1, 2 and 3 together make:

    void lcl_adjustMenuItemIDs( Menu& _rMenu, IController&
_rCommandController )
    {
        USHORT nCount = _rMenu.GetItemCount();
            for ( USHORT pos = 0; pos < nCount; ++pos )
            {
            // do NOT adjust SEPARATORS!
            MenuItemType nItemType = _rMenu.GetItemType( pos );
            if ( nItemType == MENUITEM_SEPARATOR )
                continue;

            USHORT nId = _rMenu.GetItemId(pos);
            String aCommand = _rMenu.GetItemCommand( nId );
            PopupMenu* pPopup = _rMenu.GetPopupMenu( nId );
            if ( pPopup )
            {
                lcl_adjustMenuItemIDs( *pPopup, _rCommandController );
                continue;
            }

            USHORT nCommandId = _rCommandController.registerCommandURL(
aCommand );
            _rMenu.InsertItem( nCommandId, _rMenu.GetItemText( nId ),
_rMenu.GetItemBits( nId ), pos );

            // first preserve IMAGES!
            Image aImage = _rMenu.GetItemImage( nId );
            if ( !!aImage )
                _rMenu.SetItemImage( nCommandId, aImage );

            // and preserve HELP command URL
            ::rtl::OUString aHelpURL = _rMenu.GetHelpCommand( nId );
            if ( aHelpURL.getLength() )
                _rMenu.SetHelpCommand(  nCommandId, aHelpURL  );

            _rMenu.RemoveItem( pos+1 );
            }
    }

This code makes the PopupMenu look like the image in
http://www.ArielConstenlaHaile.com.ar/ooo/tmp/dbaccess/with_seps_and_menu_item_image.png


Ad 4.: the issue for the command not being dispatched is a complex one:

 *  when the user chooses a context menu item,
    DBTreeListBox::ExcecuteContextMenuAction invokes
    OApplicationController::executeChecked(sal_uInt16 _nCommandId,
    const Sequence< PropertyValue >& aArgs) with the item ID of
    the selected menu item

 *  OApplicationController::executeChecked forwards the call to
    OGenericUnoController::executeChecked

 *  OGenericUnoController::executeChecked will
      * first check if the command corresponding to this ID is
        enabled [isCommandEnabled(_nCommandId)]
      * then, if enable, execute it

The first problem here is when isCommandEnabled checks the feature state:

* OApplicationController::GetState switches all the IDs it can handle
  and as <code>default</code> forwards to its base class, this will be
  the case when the ID corresponds to a command added by a context menu
  interceptor
* but in OGenericUnoController::GetState there is no code enabling so
  called "user defined features", so it returns always in this case a
  feature automatically disabled, and the command is then never executed

A very dummy hack enabling all "user defined features"


FeatureState OGenericUnoController::GetState(sal_uInt16 nId) const
{
        FeatureState aReturn;
                // (disabled automatically)

        try
        {
                switch (nId)
                {
                        case ID_BROWSER_UNDO:
                        case ID_BROWSER_SAVEDOC:
                                aReturn.bEnabled = sal_True;
                                break;
            default:
                if ( ( nId >= FIRST_USER_DEFINED_FEATURE ) && ( nId <
LAST_USER_DEFINED_FEATURE ) )
                {
                    aReturn.bEnabled = sal_True;
                }
                }
        }
        catch( const Exception& )
        {
        DBG_UNHANDLED_EXCEPTION();
        }

        return aReturn;
}


does not solve the problem, and discovers new ones:

* isCommandEnabled returns true (because we force
  GetState( _nCommandId ).bEnabled = sal_True for all
  "user defined features", so the command ID will be
  executed

* OApplicationController::Execute(sal_uInt16 _nId,
  const Sequence< PropertyValue >& aArgs) forwards
  to its base when the ID belongs to a "user defined features"

* OGenericUnoController::Execute
  *  first gets the fully parsed command URL for this command ID
  *  then queries a dispatch object
  *  and ask the dispatch to execute the URL

The problem in these last steps is that
OGenericUnoController::queryDispatch checks that the command is among
its supported features and returns itself as dispatch object, even when
it is a "user defined features". So that looking at the code


void OGenericUnoController::Execute( sal_uInt16 _nId, const Sequence<
PropertyValue>& _rArgs )
{

    OSL_ENSURE( isUserDefinedFeature( _nId ),
        "OGenericUnoController::Execute: responsible for user defined
features only!" );

    URL aFeatureURL( getURLForId( _nId ) );

    // user defined features can be handled by dispatch interceptors
only. So, we need to do
    // a queryDispatch, and dispatch the URL
    try
    {
        Reference< XDispatch > xDispatch( queryDispatch(
            aFeatureURL,
            ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "_self" ) ),
            FrameSearchFlag::AUTO
        ) );

        if ( xDispatch == *this )
            xDispatch.clear();

        if ( xDispatch.is() )
            xDispatch->dispatch( aFeatureURL, _rArgs );
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION();
    }
}

it's is obvious that:

* the controller returns *itself* as dispatch for the command
* but then the reference is *cleared*
* so the dispatch *never* dispatches the URL

Not clearing the reference does not solve anything: the application
controller won't dispatch the URL anyway.

Forcing to query the slave dispatch providers when it is a user defined
feature won't either solve the problem: I'm not sure if the comment

"user defined features can be handled by dispatch interceptors *only*.
So, we need to do a queryDispatch, and dispatch the URL"

is completely wrong (I never use dispatch interceptors because it is
quite useless, as it may happen that more than one registers for the
same command and nobody can assure who wins the battle - the same
happens with the context menu interceptors, but here there is no other
way to achieve the same, while in the other case a ProtocolHandler is
more convenient), but I'm sure that:

* a context menu interceptor can add commands handled by itself, so that
  its own ProtocolHandler is the one that is queried for a dispatch
* this way also an extension binds commands to images, and the images
  are display in the context menu
* again, the extension's ProtocolHandler is queried for a dispatch, not
  the controller, nor slaves dispatches

I tried the dummy hack of querying the controller's frame for a dispatch
object, but that does not work:



void OGenericUnoController::Execute( sal_uInt16 _nId, const Sequence<
PropertyValue>& _rArgs )
{
    OSL_ENSURE( isUserDefinedFeature( _nId ),
        "OGenericUnoController::Execute: responsible for user defined
features only!" );

    URL aFeatureURL( getURLForId( _nId ) );

    // user defined features can be handled by dispatch interceptors
only. So, we need to do
    // a queryDispatch, and dispatch the URL
    try
    {
        Reference< XDispatchProvider > xDispatchProvider( getFrame(),
UNO_QUERY );
        Reference< XDispatch > xDispatch;

        if ( xDispatchProvider.is() )
        {
            xDispatch = xDispatchProvider->queryDispatch(
                            aFeatureURL,
                            ::rtl::OUString(
RTL_CONSTASCII_USTRINGPARAM( "_self" ) ),
                            FrameSearchFlag::AUTO );
            OSL_TRACE("trying with getFrame()");
        }
        /*Reference< XDispatch > xDispatch( queryDispatch(
            aFeatureURL,
            ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "_self" ) ),
            FrameSearchFlag::AUTO
        ) );*/

        if ( xDispatch == *this )
            xDispatch.clear();

        if ( xDispatch.is() )
            xDispatch->dispatch( aFeatureURL, _rArgs );
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION();
    }
}


I thought that getFrame() will return an css.frame.XFrame reference to
the controller's frame, and querying the frame for a dispatch for my
URL, it will query in turn my ProtocolHandler [as stated in
http://api.openoffice.org/docs/common/ref/com/sun/star/frame/ProtocolHandler.html:
"The generic dispatch mechanism on a Frame search for such registered
protocol handler and use it if it agrees with the dispatched URL."]

Unfortunately didn't have time to search what's behind this getFrame(),
but if we compare what Writer/Calc/Draw/Impress do if we query
for a dispatch for an "user defined command":

* if Writer/Calc/...'s *controller* is the dispatch provider for
  our command, returns a *null* dispatch
* if Writer/Calc/...'s *frame* is the dispatch provider, the "generic
  dispatch mechanism on a Frame" will find that our ProtocolHandler
  can handle this URL and will query it for a dispatch

But in the case of Base:

* if Base's *controller* is the dispatch provider for our command,
  it returns *itself* (the OApplicationController) as dispatch
  [remember that this is because our feature is among its
  "supported" ones]
* if Base's *frame* is the dispatch provider, it also returns the
  aplication controller in queryDispatch


The following OOo Basic code shows this facts:

Sub DispatchProvider_tester
        
        Dim oURLTransformer as Object
        oURLTransformer  = createUnoService("com.sun.star.util.URLTransformer")
        
        Dim aURL as New com.sun.star.util.URL
        aURL.Complete =
"ar.com.arielconstenlahaile.openoffice.Menuinterceptor:function1"
        
        oURLTransformer.parseStrict(aURL)

        Dim oDoc as Object
        oDoc = loadNewDoc("writer")
        Frame_DispatchProvider(oDoc, aURL)
        Controller_DispatchProvider(oDoc, aURL)
        
        oDoc = loadNewDoc("calc")
        Frame_DispatchProvider(oDoc, aURL)
        Controller_DispatchProvider(oDoc, aURL)

        oDoc = loadNewDoc("draw")
        Frame_DispatchProvider(oDoc, aURL)
        Controller_DispatchProvider(oDoc, aURL)
        
End Sub

Function loadNewDoc(sDocType$)
        loadNewDoc = StarDesktop.loadComponentFromURL("private:factory/s" &
sDocType, "_blank",0,Array())
End Function

Sub Frame_DispatchProvider(oDoc as Object, aURL as com.sun.star.util.URL)

        Dim oDispatchProvider as Object
        oDispatchProvider = oDoc.getCurrentController().getFrame()
        
        Dim oDispatch as Object
        oDispatch = oDispatchProvider.queryDispatch(aURL, "_self", 0)
        
        If NOT IsNull(oDispatch) Then
                oDispatch.dispatch(aURL, Array())
        End If
End Sub

Sub Controller_DispatchProvider(oDoc as Object, aURL as
com.sun.star.util.URL)

        Dim oDispatchProvider as Object
        oDispatchProvider = oDoc.getCurrentController()
        
        Dim oDispatch as Object
        oDispatch = oDispatchProvider.queryDispatch(aURL, "_self", 0)
        
        If NOT IsNull(oDispatch) Then
                oDispatch.dispatch(aURL, Array())
        End If
End Sub


If we add some lines to test it with a data base document, in both cases
oDispatch is the application controller. From a Writer/etc. doc., in
Frame_DispatchProvider it is my ProtocolHandler impl. that returns
itself as dispatch object, but in Controller_DispatchProvider, oDispatch
is null.

I only had time to investigate up to this point, so I continue with the
other feature.

**********************************************************************
**********************************************************************

com.sun.star.view.XSelectionSupplier

* if the com.sun.star.view.XSelectionChangeListener is added by a Job
OnLoad/OnNew this will crash OOo: see in
OAppDetailPageHelper::describeCurrentSelectionForType, the OSL_ENSURE at
 dbaccess/source/ui/app/AppDetailPageHelper.cxx v. 1.33 line 475.

* as in the previous implementation, there is an extra notification (see
http://www.openoffice.org/issues/show_bug.cgi?id=86745)

* an issue that was present in the previous impl. and was solved there
but comes back now is that there is no notification for the icon choice
ctrl. selection on the app. swap window
(http://www.openoffice.org/issues/show_bug.cgi?id=69740)


**********************************************************************
**********************************************************************

I'll try in the week to test the other app. controller
features with the demo extension and let you know. Hope this helps.

Regards
Ariel.


[1] All what follows is just a description by reading the sources, I
didn't have time to debug this, so don't take me too seriously.
And I'm so lazy that don't quote any files, obviously
Frank knows where to find the classes and functions I'm talking about.
If others find this interesting, please forgive the omission; most code
is in
dbaccess/source/ui/app
dbaccess/source/ui/browser
dbaccess/source/ui/control
The PopupMenu code is in vcl, and in framework the helper to convert
from-to ActionTriggerContainer.
All comments are based on a DEV300_m25 build.

--
Ariel Constenla-Haile
La Plata, Argentina

[EMAIL PROTECTED]
[EMAIL PROTECTED]

http://www.ArielConstenlaHaile.com.ar/ooo/



"Aus der Kriegsschule des Lebens
                - Was mich nicht umbringt,
        macht mich härter."
                Nietzsche Götzendämmerung, Sprüche und Pfeile, 8.


---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to