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]