2017-12-31

37: How to Listen to Spread Sheet Mouse Click Events and Keystroke Events

<The previous article in this series | The table of contents of this series | The next article in this series>

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: LibreOffice

About: Apache OpenOffice

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

References

  • Apache OpenOffice Wiki. (2014/01/02). Apache OpenOffice Developer's Guide. Retrieved from https://wiki.openoffice.org/wiki/Documentation/DevGuide/OpenOffice.org_Developers_Guide

<The previous article in this series | The table of contents of this series | The next article in this series>