Main Body START
How to Implement and Register Listeners of Spread Sheet Mouse Click Events and Keystroke Events in LibreOffice or Apache OpenOffice Is Explained
About: UNO (Universal Network Objects)
About: Java Programming Language
Necessity for Listening to Spread Sheet Mouse Click Events and Keystroke Events
Here are -Hypothesizer and -Rebutter sitting in front of a computer screen in a room on a brook among some mountains on the Bias planet.
-Hypothesizer
We need to listen to spread sheet mouse click events and keystroke events, because we are using spread sheets as GUI components of our applications. That is, we aren't just handling spread sheet documents as files.
-Rebutter
We won't necessarily need to listen to those events because we are using spread sheets as GUI components . . .
-Hypothesizer
Well, we may not necessarily need to do so, but without doing so, the user interfaces of some of our applications can't avoid being very frustrating.
-Rebutter
Some? What some?
-Hypothesizer
I think, a user interface is frustrating when it requires the user to do what, the user feels, shouldn't be required. I mean, by requiring to push a button after the user clicks a cell, the application can know the occurrence of the click, but the user would feel, "Why should I be required to push the damn button just for notifying the damn application my click? I already clearly showed my intention by clicking the damn cell!"
-Rebutter
Is your user ill-bred, using a word like 'damn' three times in just two sentences?
-Hypothesizer
Actually, the user is nobody but me.
-Rebutter
So, the answer is yes. Anyway, the button or the cell isn't a one to be damned, for they aren't ones to be blamed.
-Hypothesizer
. . . I will retain 'damn' only for the application: the application is damn.
Anyway, in order to push the button, the user has to move the mouse cursor from the cell to the button, often very far, and often he or she has to move the mouse cursor from the button to another cell near the previously clicked cell. And such operations are often repetitive.
-Rebutter
In the first place, why are we using spread sheets as GUI components?
-Hypothesizer
Because the spread sheet user interface of LibreOffice is handy: we can set functions into cells, which are automatically refreshed as other cells are changed, can set fonts, colors, etc. into cells or characters, can do spell checks, can merge cells, can easily input serial data, can create charts, . . .. Creating our own user interface that parallels the spread sheet user interface of LibreOffice wouldn't be insignificant task. So, we want to take advantage of the spread sheet user interface of LibreOffice, and incorporate our logics into the user interface.
-Rebutter
In fact, don't we want to listen to events of the current cell movement, not mouse click events or keystroke events themselves?
-Hypothesizer
Yes. Our present concern is to listen to events of the current cell movement, but regrettably, we can't find out how to directly do so. We want to listen to mouse click events and keystroke events as a way to infer the current cell movement.
How to Implement and Register Listeners of Spread Sheet Mouse Click Events and Keystroke Events
The same with the previous scene.
-Hypothesizer
As for mouse click events, the controller implements an interface, 'com.sun.star.sheet.XEnhancedMouseClickBroadcaster', that accepts listeners that implement 'com.sun.star.awt.XEnhancedMouseClickHandler'; as for keystroke events, the controller implements an interface, 'com.sun.star.awt.XUserInputInterception', that accepts listeners that implement 'com.sun.star.awt.XKeyHandler'.
This time, we will make the 'thebiasplanet.unoextensiontests.controllers.TestController' class of the 'testUnoExtensionToDisclose' project the listener of both kinds of events, like this.
-Hypothesizer edits the 'thebiasplanet.unoextensiontests.controllers.TestController' class (he used their utility classes, 'thebiasplanet.unoutilities.documentshandling.spreadsheetsdocumentshandling.UnoSpreadSheet', 'thebiasplanet.unoutilities.documentshandling.spreadsheetsdocumentshandling.UnoSpreadSheetsDocument', and 'thebiasplanet.coreutilities.messaging.Publisher'; how to get the reference to the controller is described here; the method, 'appendToFile', appends the specified string into the specified file). -Hypothesizer also added a button, 'Set the Events Listeners', into the control panel.
@
Java Source Code
package thebiasplanet.unoextensiontests.controllers;
~~~ Import directives irrelevant here are omitted. ~~~
import java.net.URL;
import com.sun.star.awt.EnhancedMouseEvent;
import com.sun.star.awt.KeyEvent;
import com.sun.star.awt.XEnhancedMouseClickHandler;
import com.sun.star.awt.XKeyHandler;
import com.sun.star.awt.XUserInputInterception;
import com.sun.star.beans.XPropertySet;
import com.sun.star.lang.EventObject;
import com.sun.star.sheet.XEnhancedMouseClickBroadcaster;
import com.sun.star.sheet.XSpreadsheetView;
import com.sun.star.table.XCell;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XComponentContext;
import thebiasplanet.coreutilities.constantsgroups.*;
import thebiasplanet.coreutilities.messaging.Publisher;
import thebiasplanet.unoutilities.documentshandling.spreadsheetsdocumentshandling.UnoSpreadSheet;
import thebiasplanet.unoutilities.documentshandling.spreadsheetsdocumentshandling.UnoSpreadSheetsDocument;
public class TestController implements XEnhancedMouseClickHandler, XKeyHandler {
private XComponentContext i_componentContextInXComponentContext;
private String i_openedDocumentDirectoryPath;
public TestController (XComponentContext a_componentContextInXComponentContext) {
i_componentContextInXComponentContext = a_componentContextInXComponentContext;
}
~~~ Parts irrelevant here are omitted. ~~~
public void setEventsListeners () throws Exception {
UnoSpreadSheet l_currentSpreadSheet = UnoSpreadSheet.getCurrentSpreadSheet (i_componentContextInXComponentContext);
if (l_currentSpreadSheet != null) {
UnoSpreadSheetsDocument l_currentSpreadSheetsDocument = l_currentSpreadSheet.getSpreadSheetsDocument ();
XSpreadsheetView l_controllerInXSpreadsheetView = l_currentSpreadSheetsDocument.getControllerInXSpreadsheetView ();
XEnhancedMouseClickBroadcaster l_controllerInXEnhancedMouseClickBroadcaster = (XEnhancedMouseClickBroadcaster) UnoRuntime.queryInterface (XEnhancedMouseClickBroadcaster.class, l_controllerInXSpreadsheetView);
l_controllerInXEnhancedMouseClickBroadcaster.addEnhancedMouseClickHandler (this);
XUserInputInterception l_controllerInXUserInputInterception = (XUserInputInterception) UnoRuntime.queryInterface (XUserInputInterception.class, l_controllerInXSpreadsheetView);
l_controllerInXUserInputInterception.addKeyHandler (this);
String l_openedDocumentFilePath = (new URL (l_currentSpreadSheetsDocument.getLocationUrl ())).getFile ();
i_openedDocumentDirectoryPath = l_openedDocumentFilePath.substring (0, l_openedDocumentFilePath.lastIndexOf ("/"));
}
}
private String getCellAbsoluteName (XCell a_cellInXCell) {
XPropertySet l_cellInXPropertySet = (XPropertySet) UnoRuntime.queryInterface (XPropertySet.class, a_cellInXCell);
try {
return (String) l_cellInXPropertySet.getPropertyValue ("AbsoluteName");
}
catch (Exception l_exception) {
// This will never happen.
return null;
}
}
@Override
public void disposing (EventObject a_event) {
}
@Override
public boolean mousePressed (EnhancedMouseEvent a_enhancedMouseEvent) {
XCell l_mousePressedCellInXCell = (XCell) UnoRuntime.queryInterface (XCell.class, a_enhancedMouseEvent.Target);
if (l_mousePressedCellInXCell != null) {
Publisher.appendToFile ( String.format ("%s/%s", i_openedDocumentDirectoryPath, "UnoMouseEvents.txt"), String.format ("MousePressed: on %s, buttons = %d, modifiers = %d, count = %d", getCellAbsoluteName (l_mousePressedCellInXCell), a_enhancedMouseEvent.Buttons, a_enhancedMouseEvent.Modifiers, a_enhancedMouseEvent.ClickCount));
}
return true;
}
@Override
public boolean mouseReleased (EnhancedMouseEvent a_enhancedMouseEvent) {
XCell l_mouseReleasedCellInXCell = (XCell) UnoRuntime.queryInterface (XCell.class, a_enhancedMouseEvent.Target);
if (l_mouseReleasedCellInXCell != null) {
Publisher.appendToFile ( String.format ("%s/%s", i_openedDocumentDirectoryPath, "UnoMouseEvents.txt"), String.format ("MouseReleased: on %s, buttons = %d, modifiers = %d, count = %d", getCellAbsoluteName (l_mouseReleasedCellInXCell), a_enhancedMouseEvent.Buttons, a_enhancedMouseEvent.Modifiers, a_enhancedMouseEvent.ClickCount));
}
return true;
}
@Override
public boolean keyPressed (KeyEvent a_keyEvent) {
Publisher.appendToFile (String.format ("%s/%s", i_openedDocumentDirectoryPath, "UnoKeyEvents.txt"), String.format ("KeyPressed: code = %d, modifiers = %d", a_keyEvent.KeyCode, a_keyEvent.Modifiers));
return false;
}
@Override
public boolean keyReleased (KeyEvent a_keyEvent) {
Publisher.appendToFile (String.format ("%s/%s", i_openedDocumentDirectoryPath, "UnoKeyEvents.txt"), String.format ("KeyReleased: code = %d, modifiers = %d", a_keyEvent.KeyCode, a_keyEvent.Modifiers));
return false;
}
}
-Hypothesizer
As you can see, from the event arguments of the 'mousePressed' method and the 'mouseReleased' method, we can get the cell on which the mouse event happened.
-Rebutter
What if the mouse click happened not on a cell?
-Hypothesizer
. . . What do you mean? Do you mean that the mouse click happened precisely on the border between two cells?
-Rebutter
I don't assume that a mouse click can happen precisely on a border, but I know that a row label box (I don't know the official name of it. I mean a box that shows the row number, by clicking on which we can select the whole row), a column label box (please guess what it is from the explanation of 'row label box'), or the 'select-all-the-cells-box' (what is the official name of it? I guess what I mean will be understood) can be clicked.
-Hypothesizer
Well, . . . let's find out.
-Hypothesizer does some experiments.
-Hypothesizer
. . . Really?
-Hypothesizer
There doesn't seem to be any reason to doubt its being real.
-Hypothesizer
Our listener doesn't hear anything when a row label box, a column label box, or the 'select-all-the-cells-box' is clicked. . . .Such a behavior seems unreasonable, for many people who want to hear cell clicks will also want to hear row label box clicks.
-Rebutter
Is that a problem?
-Hypothesizer
That's a huge, or, I would say, critical problem! As mouse events on row label boxs move the current cell, we have to detect such mouse events in order to infer the movement of the current cell.
-Rebutter
Isn't there a way to infer the movement of the current cell without detecting such mouse events?
-Hypothesizer
There isn't any. I thought of Inferring it from the change of the selection, but that's impossible.
For example, there are these two cases in which the changes of the selection are the same but the movements of the current cell are different.
In the first case, we press (and not release) the mouse button on the 9th row label box, drag the mouse cursor to the 7th row, drag back the mouse cursor to the 8th row, and release the button. The changes of the selection are 'A9:AMJ9' -> 'A7:AMJ9' -> 'A8:AMJ9'; the last current cell is at 'A8'.
-Rebutter
At 'A8'? That behavior feels inconsistent with the behavior of when the mouse cursor is dragged over cells: in the latter, the current cell doesn't move as the mouse cursor is dragged.
-Hypothesizer
Certainly, but that's the way it really is.
In the second case, we press (and not release) the mouse button on the 9th row label box, drag the mouse cursor to the 7th row, release the button, and click with the control key pressed on the 7th row label box. The changes of the selection are 'A9:AMJ9' -> 'A7:AMJ9' -> 'A8:AMJ9', the same with the first case; the last current cell is at 'A7', different from the first case.
-Rebutter
Well, when 'select-all-the-cells-box' is clicked, where does the current cell go?
-Hypothesizer
In fact, it doesn't go anywhere.
-Rebutter
That behavior also feels inconsistent with the behavior of when a row or column label box is clicked. Why doesn't the current cell move in the former, while it does in the latter?
-Hypothesizer
I don't know, but that's the way it is.
-Rebutter
Well, isn't there another type of mouse events listener we can use?
-Hypothesizer
Hmm, the interface, 'com.sun.star.awt.XUserInputInterception', also accepts mouse clicks listeners, and the interface, 'XWindow', implemented by the spread sheet window also accepts mouse clicks listeners. . . . I wonder whether those listeners can hear what we want to hear.
-Hypothesizer does some experiments with those listeners, taking some time.
-Hypothesizer
. . . You know what, those listeners can't hear what we want to hear, and especially, the latter doesn't hear anything.
-Rebutter
Why?
-Hypothesizer
I don't know. Probably, events are intercepted and consumed by another listener.
-Rebutter
Well, we don't have to blindly obey the way it is, at least when the way it is is inconsistent or unreasonable, do we?
-Hypothesizer
. . . Being said "blindly obey", as far as the current cell moves, we will have to infer the movement as it does . . .
-Rebutter
I thought we could move the current cell from our programs.
-Hypothesizer
Ah, . . . so, we would control the current cell movement, not just infer it?
-Rebutter
If possible.
-Hypothesizer
In fact, we don't know how to move only the current cell without any side effect: the existing selection will be nullified by our moving the current cell.
-Rebutter
Can't we reselect the previous selection area from our programs?
-Hypothesizer
Well, . . . we could, but that seems too much: it feels like we are sinking into a bottomless swamp, just for rather infrequent cases . . .
-Rebutter
Well, giving up on some infrequent cases can be a viable solution, although it may be somewhat ugly.
-Hypothesizer
I will consider what to do . . .
-Rebutter
As for keystroke events, how can we know the cell that received the keystroke?
-Hypothesizer
As far as I look at the event argument, we seem to not be able to know that.
-Rebutter
Hmm, . . . is that OK?
-Hypothesizer
Well, I thought that if we successfully managed to infer the position of the current cell, that cell would be the cell that received the keystroke. However, that precondition seems to have to be conditional.
-Rebutter
What are those return values of the listener methods?
-Hypothesizer
Actually, there is no explanation for the return value of the 'mousePressed' method or the 'mouseReleased' method in the API document, but the return value of 'false' seems to mean 'not forwarding the event'.
-Rebutter
No explanation at all?
-Hypothesizer
The API document says that the return type is boolean, and that's the whole of the explanation, unless some kind of secret ink is used.
-Rebutter
What exactly will happen if the method returns 'false'?
-Hypothesizer
The current cell won't move to the clicked cell, if, of course, we don't move it from our method. So, the user will feel as though his or her click was ignored.
-Rebutter
Why do we return 'false' for the 'keyPressed' method and the 'keyReleased' method?
-Hypothesizer
Because for those methods, returning 'true' means 'not forwarding the event'.
-Rebutter
So, is the meaning of the return of the 'mousePressed' method opposite to that of the 'keyPressed' method?
-Hypothesizer
I guessed so.
-Rebutter
Are those events fired before the selection change events are fired?
-Hypothesizer
Yes. As we can know from the fact that we can block the movement of the current cell by the return values, at the time when those methods are called, the selection isn't changed yet.
Our test code is included in this zip file, and how to use the zip file is described in this article. Opening the 'TestSpreadSheetsDocumentForExecutingTests.ods' file, clicking the 'Show Test Control Panel' button, and clicking the 'Set the Events Listeners' button will begin listening to events. Events detected will be recorded into the files, 'UnoMouseEvents.txt' or 'UnoKeyEvents.txt', in the 'execution' directory of the 'testUnoExtensionToDisclose' project.
Main Body END