Hi Gerhard,
Schuster Gerhard escribió:
Hi all,
I'm struggling with context menu interception and would apreciate
your help.
you have read my mind. Next week I was going to send a mail to
[EMAIL PROTECTED] with the subject "How to implement a context menu
interceptor in the real world"
I will try first to "answer" your questions, then I will try to give you
some explanations about what I've been doing.
But let me tell you that more than "answers", I will (surely) complicate
even more your situation :-(
This are my questions:
a) In OO-Writer the context menu "Font" has a sub menu and an icon, but
the properties of the context menu "Font" are this:
Property name = >CommandURL< Value= >.uno:CharFontName<
Property name = >HelpURL< Value= ><
Property name = >Image< Value= >Any[Type[com.sun.star.awt.XBitmap],
null]<
Property name = >SubContainer< Value= >null<
Property name = >Text< Value= >Font<
i.e. there is no SubContainer and no Image specified.
So there must be another mechanism to specify context menues but
the XPropertySet. Where is that documented?
if you see the menu item "Style" ( "CommandURL"=".uno:StyleMenu"),
getPropertyValue("SubContainer") returns an object not null, telling
that it has a submenu, you cast/convert this to an XIndexContainer to
access the submenu.
The menu item for ".uno:TransliterateMenu" has also a submenu, and also
".uno:LineSpacingMenu", .uno:AlignTextMenu", etc.
You can access this submenus and add/remove items as indicated in the
Dev's Guide example.
For the menu item with "CommandURL"=".uno:CharFontName",
getPropertyValue("SubContainer") returns a null object reference; I
think that here, there is a logical explanation (a Sun engineer could
tell us if this is true) : as fonts can be added by the user in the OS,
OOo intercepts the context menu and adds this submenu with the fonts
names at runtime (if you try installing a new font system level, not
just inside OOo, you'll see that the context menu after you install it
lists the new font)
Concerning getPropertyValue("Image"), I think that something's broken
with the API (or I'm too fool make it work ;-) ): all I can get from the
object returned by getPropertyValue("Image") is a NULL reference to
XBitmap, this happens in every menu item. I'm also unable to set an
XBitmap reference to new menu items I add.
b) I've noticed that the context menu of OO-Writer contains items hat are not
visible.
for the context menu triggered when the cursor is in the text body and
nothing is selected I counted 36 items on the top level (sub menus are
not included in this count)
It seems like OOo gives us the list of *available* menu items for this
context, not only the items OOo will *actually* display for this context.
An example: "slot:10955" is the command URL for "Open hyperlink".
This item will only be displayed by OOo if the mouse is over an
hyperlink or the cursor is inside it. Nevertheless, if we query the
context menu triggered by a collapsed cursor in the text body, we will
get this menu item at index 30 (at least in OOo 2.3.0), BUT the item
won't be visible in the executed context menu unless there is an hyperlink.
In chapter 4.7.5 of the develoipers guide I read:
"It is possible to accomplish certain tasks without implementing code in a
context menu interceptor,
such as preventing a context menu from being activated. Normally, a context
menu is changed to
provide additional functions to the user."
My question is:
b.1) What are this certain tasks?
b.2) How are this tasks performed, such as preventing a context menu
from being activated?
You can add new items, remove all items, prevent the context menu for
being executed, etc.
Your XContextMenuInterceptor implementation must have a method named
notifyContextMenuExecute, with this method you intercept the context
menu before being executed.
Depending on the value you return (one of the enum
ContextMenuInterceptorAction), you can change the context menu, cancel
its execution, etc. The example in the Dev's Guide is clear.
The event object delivered to this method (ContextMenuExecuteEvent) has
a member ActionTriggerContainer, it is an XIndexContainer == you can
insert and remove elements == menu items.
I think these are the "certain tasks" the Dev's Guide is talking about,
and depend of the return value of your interceptor and how you
manipulate the XIndexContainer.
Other "additional functions" can be introduced by new command URLs, as I
tell you later...
c) When I introduce my own context menu items they work fine until I
add a CommandURL. Once I've add a CommandURL the item gets disabled.
Here is the item:
Property name = >CommandURL< Value= >BlaFasel:12346<
Property name = >HelpURL< Value= ><
Property name = >Image< Value= >Any[Type[com.sun.star.awt.XBitmap],
null]<
Property name = >SubContainer< Value= >null<
Property name = >Text< Value= >Next entry<
I've installed a XDispatchProviderInterceptor for BlaFasel:*, which
prints out "***** url complete: BlaFasel:12346... works!" in its
queryDispatch method whenever the context menu is opened, but the
menu item is still not visible.
My question is: How do I implement a context menu item and stick my
own callback to it so that the item is still visible.
For this, you have to implement a ProtocolHandler. The Dev's Guide
explains how to do this.
Some quotes about what a protocol handler is:
"The dispatch framework binds user interface controls, such as menu or
toolbar items, to the functionality of OpenOffice.org.
Every function that is reachable in the user interface is described by a
command URL and corresponding parameters.
The protocol handler mechanism is an API that enables programmers to
add arbitrary URL schemas to the existing set of command URLs
by writing additional protocol handlers for them.
Such a protocol handler must be implemented as a UNO component and
registered in the OpenOffice.org configuration for the new URL schema."
This means, you need a UNO component implementing a protocol handler,
registered at OOo config. using a file ProtocolHandler.xcu,
for example:
<?xml version='1.0' encoding='UTF-8'?>
<oor:component-data
oor:package="org.openoffice.Office"
oor:name="ProtocolHandler"
xmlns:oor="http://openoffice.org/2001/registry"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<node oor:name="HandlerSet">
<node
oor:name="ar.com.arielconstenlahaile.openoffice.menuinterceptor.ProtocolHandlerImpl"
oor:op="replace">
<prop oor:name="Protocols" oor:type="oor:string-list">
<value>ar.com.arielconstenlahaile.openoffice.Menuinterceptor:*</value>
</prop>
</node>
</node>
</oor:component-data>
In this example:
ar.com.arielconstenlahaile.openoffice.menuinterceptor.ProtocolHandlerImpl
is the name of the Java class with the implementation of the
ProtocolHandler (in Java the implementation name is/can be the same as
the class name). The JAR must be indicated with an entry in the manifest
of the extension.
ar.com.arielconstenlahaile.openoffice.Menuinterceptor:*
is the URL schema I've chosen. I use a place holder "*" so I use the
same schema in every menu item, for example:
"ar.com.arielconstenlahaile.openoffice.Menuinterceptor:openCalcDoc"
"ar.com.arielconstenlahaile.openoffice.Menuinterceptor:openWriterDoc"
As my URL schema is registered in OOo configuration registry, if I use
this CommandURLs for my new items, they are enabled, and when the user
chooses the menu item, the Dispatch Framework will process this request
and look in the chain of responsability for a component willing to
handle it: my ProtocolHandler implementation will be asked to dispatch it.
*************************************************************************
*************************************************************************
The following is my experience with the interceptor. You can skip this
part: take this as simple thoughts, not the truth about the topic. And
I'm sure that if you read this, I will make your life more complicated :-(
**************************************************************************
**************************************************************************
If you want to intercept a context menu with your extension IN THE REAL
WORLD,
* first you have to implement an XJob and register your UNO component to
be executed by the job execution environment upon the events "OnLoad"
and "OnNew". That is: your extension wants obviously to intercept the
context menu in *EVERY* Writer doc., for example; so your UNO component
will be call every time a doc is loaded (OnLoad) from an existing
location, and every time a new doc is created (OnNew). In your XJob
implementation you have to detected if the doc is a Writer doc. and then
get the controller and register your context menu interceptor
* if you want to use your own Command URL, you have to implement a
ProtocolHandler, as I said before. [note that you can use command URLs
"pointing" to OOoBasic macros, or other scripts. In these cases you
won't need a ProtocolHandler impl.]
But things are even more complicated: I've found two problems (you can
do your own testings in a Writer doc.):
1. if the users chooses to see a PREVIEW of the document, when the
document is back to the NORMAL VIEW, your context menu interceptor does
not work anymore
2. if the user chooses to see the doc. in a new window (menu "Window" -
"New window") your context menu interceptor does not work anymore
These are the reasons I could find (I'm not very sure they are right :-(
), I will use "my terms", not the technical ones [as I'm not sure how it
works "internally"], sure a Sun engineer will laugh at them)
*****************************************************************************
1. PREVIEW: here the controller you registered your interceptor gets
disposed. Your XJob won't be called: no new document is loaded nor your
document is loaded once again for the "normal-view": the component gets
detached form the frame and then reattached again. A new controller is
created each time for each view.
The reason I think is that the Framework (the Layoutmanager ?) destroys
every element of the current "normal" view in order to create a new
view= a PREVIEW of the document; so the component is detached from the
frame (== the controller is disposed), and then reattached (a new
controller is created that controls the new view). When the user "goes
back" to the normal view, the same operation happens again: component
detached + preview-controller disposed, then component reattached + new
normal-view-controller created.
As you see, the controller where you registered your interceptor is
disposed, when the user comes back to the normal view, a NEW controller
is created, and you will have to register your interceptor "again"
[technically: for the first time with this new controller].
Solution: in your XJob (gets called every time a new doc is created or
an existing one is loaded) add an XFrameActionListener to the Frame the
component is attached to. You will get notified for every frame action.
If the frame action event (FrameActionEvent.Action) is
FrameAction.COMPONENT_ATTACHED or FrameAction.COMPONENT_REATTACHED, get
the controller and add your interceptor again
[here there are some problems:
a. if you do not want to intercept the preview-controller, things are
more complicated :-( ,
b. be sure to get the controller form the frame
(FrameActionEvent.Frame.getController(). I was stupid getting the model
from the frame (I needed a model reference) and THEN getting the
controller form the model (getCurrentController() WILL RETURN NULL, it
looks like at this moment there is actually a controller, but NOT a
*current* controller)]
The XFrameActionListener adds another complication(sorry :-( again): the
document can be closed/disposed, but the frame can *still* be there (for
example if you load a component in this frame, the new component will
displace the one whose controller you intercepted; and if there is only
one doc. in the Desktop and you close the doc. with the X at the top
right corner of the menu bar, not with the X in the window title bar,
your intercepted document will be closed, but the frame will remain with
the so called StartModule in it)
In these cases, your XFrameActionListener will *still* listen for frame
actions after your doc. is gone.
Solution: in the XJob impl. add a close listener (XCloseListener) to the
document model and when it gets called, remove the frame action listener
from the frame.
So in your XJob implementation you have to:
* add the context menu interceptor to the controller
* add a frame action listener to the frame (to register the interceptor
"again" if the controller gets disposed == when the component is
deatached from the frame to create a preview)
* add a close listener to the model, so that you can remove the frame
action listener in order to stop listening for frame actions once your
document is closed
*****************************************************************************
2. NEW WINDOW: when the user chooses to see the doc. in a new window, a
NEW controller is created; you will have to register your interceptor
with this new controller.
Your XJob impl. WON'T be called: in this case a new window is just a new
view of the same model, the component is not loaded once again (so the
OnLoad event won't trigger a call to your XJob).
Solution: in your XJob impl. add a com.sun.star.document.XEventListener
to the doc. In notifyEvent() see if
com.sun.star.document.EventObject.EventName is "OnViewCreated", that is:
a new view has been created for the same doc. model. So get the
controller from the model (EventObject.Source) and register your
interceptor.
Just to complicate your life even more: you have to repeat all the
things I've been telling in this case: this new view can be switched to
a preview, so besides registering the interceptor you have to add the
frame action listener, ...... (you won't have the NEW_WINDOW problem:
remember that it's always the same document model, and you have already
added an com.sun.star.document.XEventListener to it).
*************************************************************************
Well... that's all I can remember at the moment...I will have to check
my sources again to see if I'm missing some point.
I'm sorry if I couldn't help you, and instead complicate your life even
more.
The Dev's Guide example is OK and works fine, but intercepting a context
menu IN THE REAL WORLD is much complicated.
A complete, up-to-date example from the Framework team developers will
help us a lot!
Bye and luck
Ariel.
--
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]