Version 5.2.6.1, tag libreoffice-5.2.6.1
[LibreOffice.git] / android / source / src / java / org / libreoffice / InvalidationHandler.java
blobaca50b0422cfaeace4e9c2056a941753355587a3
1 package org.libreoffice;
3 import android.content.Intent;
4 import android.graphics.PointF;
5 import android.graphics.RectF;
6 import android.net.Uri;
7 import android.util.Log;
9 import org.libreoffice.canvas.SelectionHandle;
10 import org.libreoffice.kit.Document;
11 import org.libreoffice.overlay.DocumentOverlay;
12 import org.mozilla.gecko.gfx.GeckoLayerClient;
14 import java.util.ArrayList;
15 import java.util.Collections;
16 import java.util.List;
18 /**
19 * Parses (interprets) and handles invalidation messages from LibreOffice.
21 public class InvalidationHandler implements Document.MessageCallback {
22 private static String LOGTAG = InvalidationHandler.class.getSimpleName();
23 private final DocumentOverlay mDocumentOverlay;
24 private final GeckoLayerClient mLayerClient;
25 private OverlayState mState;
26 private boolean mKeyEvent = false;
28 public InvalidationHandler(LibreOfficeMainActivity mainActivity) {
29 mDocumentOverlay = mainActivity.getDocumentOverlay();
30 mLayerClient = LibreOfficeMainActivity.getLayerClient();
31 mState = OverlayState.NONE;
34 /**
35 * Processes callback message
37 * @param messageID - ID of the message
38 * @param payload - additional invalidation message payload
40 @Override
41 public void messageRetrieved(int messageID, String payload) {
42 if (!LOKitShell.isEditingEnabled()) {
43 // enable handling of hyperlinks even in the Viewer
44 if (messageID != Document.CALLBACK_INVALIDATE_TILES && messageID != Document.CALLBACK_HYPERLINK_CLICKED)
45 return;
47 switch (messageID) {
48 case Document.CALLBACK_INVALIDATE_TILES:
49 invalidateTiles(payload);
50 break;
51 case Document.CALLBACK_INVALIDATE_VISIBLE_CURSOR:
52 invalidateCursor(payload);
53 break;
54 case Document.CALLBACK_TEXT_SELECTION:
55 textSelection(payload);
56 break;
57 case Document.CALLBACK_TEXT_SELECTION_START:
58 textSelectionStart(payload);
59 break;
60 case Document.CALLBACK_TEXT_SELECTION_END:
61 textSelectionEnd(payload);
62 break;
63 case Document.CALLBACK_CURSOR_VISIBLE:
64 cursorVisibility(payload);
65 break;
66 case Document.CALLBACK_GRAPHIC_SELECTION:
67 graphicSelection(payload);
68 break;
69 case Document.CALLBACK_HYPERLINK_CLICKED:
70 if (!payload.startsWith("http://") && !payload.startsWith("https://")) {
71 payload = "http://" + payload;
73 Intent urlIntent = new Intent(Intent.ACTION_VIEW);
74 urlIntent.setData(Uri.parse(payload));
75 LibreOfficeMainActivity.mAppContext.startActivity(urlIntent);
76 break;
77 case Document.CALLBACK_STATE_CHANGED:
78 stateChanged(payload);
79 break;
80 default:
81 Log.d(LOGTAG, "LOK_CALLBACK uncatched: " + messageID + " : " + payload);
85 private void stateChanged(String payload) {
86 String[] parts = payload.split("=");
87 if (parts.length < 2) {
88 Log.e(LOGTAG, "LOK_CALLBACK_STATE_CHANGED unexpected payload: " + payload);
89 return;
91 final String value = parts[1];
92 boolean pressed = Boolean.parseBoolean(value);
94 if (parts[0].equals(".uno:Bold")) {
95 LOKitShell.getFormattingController().onToggleStateChanged(Document.BOLD, pressed);
96 } else if (parts[0].equals(".uno:Italic")) {
97 LOKitShell.getFormattingController().onToggleStateChanged(Document.ITALIC, pressed);
98 } else if (parts[0].equals(".uno:Underline")) {
99 LOKitShell.getFormattingController().onToggleStateChanged(Document.UNDERLINE, pressed);
100 } else if (parts[0].equals(".uno:Strikeout")) {
101 LOKitShell.getFormattingController().onToggleStateChanged(Document.STRIKEOUT, pressed);
102 } else if (parts[0].equals(".uno:CharFontName")) {
103 LOKitShell.getFontController().selectFont(value);
104 } else if (parts[0].equals(".uno:FontHeight")) {
105 LOKitShell.getFontController().selectFontSize(value);
106 } else if (parts[0].equals(".uno:LeftPara")) {
107 LOKitShell.getFormattingController().onToggleStateChanged(Document.ALIGN_LEFT, pressed);
108 } else if (parts[0].equals(".uno:CenterPara")) {
109 LOKitShell.getFormattingController().onToggleStateChanged(Document.ALIGN_CENTER, pressed);
110 } else if (parts[0].equals(".uno:RightPara")) {
111 LOKitShell.getFormattingController().onToggleStateChanged(Document.ALIGN_RIGHT, pressed);
112 } else if (parts[0].equals(".uno:JustifyPara")) {
113 LOKitShell.getFormattingController().onToggleStateChanged(Document.ALIGN_JUSTIFY, pressed);
114 } else {
115 Log.d(LOGTAG, "LOK_CALLBACK_STATE_CHANGED type uncatched: " + payload);
120 * Parses the payload text with rectangle coordinates and converts to rectangle in pixel coordinates
122 * @param payload - invalidation message payload text
123 * @return rectangle in pixel coordinates
125 private RectF convertPayloadToRectangle(String payload) {
126 String payloadWithoutWhitespace = payload.replaceAll("\\s", ""); // remove all whitespace from the string
128 if (payloadWithoutWhitespace.isEmpty() || payloadWithoutWhitespace.equals("EMPTY")) {
129 return null;
132 String[] coordinates = payloadWithoutWhitespace.split(",");
134 if (coordinates.length != 4) {
135 return null;
138 int x = Integer.decode(coordinates[0]);
139 int y = Integer.decode(coordinates[1]);
140 int width = Integer.decode(coordinates[2]);
141 int height = Integer.decode(coordinates[3]);
143 float dpi = LOKitShell.getDpi();
145 return new RectF(
146 LOKitTileProvider.twipToPixel(x, dpi),
147 LOKitTileProvider.twipToPixel(y, dpi),
148 LOKitTileProvider.twipToPixel(x + width, dpi),
149 LOKitTileProvider.twipToPixel(y + height, dpi)
154 * Parses the payload text with more rectangles (separated by ';') and converts to a list of rectangles.
156 * @param payload - invalidation message payload text
157 * @return list of rectangles
159 private List<RectF> convertPayloadToRectangles(String payload) {
160 List<RectF> rectangles = new ArrayList<RectF>();
161 String[] rectangleArray = payload.split(";");
163 for (String coordinates : rectangleArray) {
164 RectF rectangle = convertPayloadToRectangle(coordinates);
165 if (rectangle != null) {
166 rectangles.add(rectangle);
171 return rectangles;
175 * Handles the tile invalidation message
177 * @param payload
179 private void invalidateTiles(String payload) {
180 RectF rectangle = convertPayloadToRectangle(payload);
181 if (rectangle != null) {
182 LOKitShell.sendTileInvalidationRequest(rectangle);
187 * Handles the cursor invalidation message
189 * @param payload
191 private synchronized void invalidateCursor(String payload) {
192 RectF cursorRectangle = convertPayloadToRectangle(payload);
193 if (cursorRectangle != null) {
194 mDocumentOverlay.positionCursor(cursorRectangle);
195 mDocumentOverlay.positionHandle(SelectionHandle.HandleType.MIDDLE, cursorRectangle);
197 if (mState == OverlayState.TRANSITION || mState == OverlayState.CURSOR) {
198 changeStateTo(OverlayState.CURSOR);
201 if (mKeyEvent) {
202 moveViewportToMakeCursorVisible(cursorRectangle);
203 mKeyEvent = false;
209 * Move the viewport to show the cursor. The cursor will appear at the
210 * viewport position depending on where the cursor is relative to the
211 * viewport (either cursor is above, below, on left or right).
213 * @param cursorRectangle - cursor position on the document
215 public void moveViewportToMakeCursorVisible(RectF cursorRectangle) {
216 RectF moveToRect = mLayerClient.getViewportMetrics().getCssViewport();
217 if (moveToRect.contains(cursorRectangle)) {
218 return;
221 float newLeft = moveToRect.left;
222 float newTop = moveToRect.top;
224 if (cursorRectangle.right < moveToRect.left || cursorRectangle.left < moveToRect.left) {
225 newLeft = cursorRectangle.left - (moveToRect.width() * 0.1f);
226 } else if (cursorRectangle.right > moveToRect.right || cursorRectangle.left > moveToRect.right) {
227 newLeft = cursorRectangle.right - (moveToRect.width() * 0.9f);
230 if (cursorRectangle.top < moveToRect.top || cursorRectangle.bottom < moveToRect.top) {
231 newTop = cursorRectangle.top - (moveToRect.height() * 0.1f);
232 } else if (cursorRectangle.bottom > moveToRect.bottom || cursorRectangle.top > moveToRect.bottom) {
233 newTop = cursorRectangle.bottom - (moveToRect.height() / 2.0f);
236 LOKitShell.moveViewportTo(new PointF(newLeft, newTop), null);
240 * Handles the text selection start message
242 * @param payload
244 private synchronized void textSelectionStart(String payload) {
245 RectF selectionRect = convertPayloadToRectangle(payload);
246 if (selectionRect != null) {
247 mDocumentOverlay.positionHandle(SelectionHandle.HandleType.START, selectionRect);
252 * Handles the text selection end message
254 * @param payload
256 private synchronized void textSelectionEnd(String payload) {
257 RectF selectionRect = convertPayloadToRectangle(payload);
258 if (selectionRect != null) {
259 mDocumentOverlay.positionHandle(SelectionHandle.HandleType.END, selectionRect);
264 * Handles the text selection message
266 * @param payload
268 private synchronized void textSelection(String payload) {
269 if (payload.isEmpty() || payload.equals("EMPTY")) {
270 if (mState == OverlayState.SELECTION) {
271 changeStateTo(OverlayState.TRANSITION);
273 mDocumentOverlay.changeSelections(Collections.EMPTY_LIST);
274 } else {
275 List<RectF> rectangles = convertPayloadToRectangles(payload);
276 if (mState != OverlayState.SELECTION) {
277 changeStateTo(OverlayState.TRANSITION);
279 changeStateTo(OverlayState.SELECTION);
280 mDocumentOverlay.changeSelections(rectangles);
285 * Handles the cursor visibility message
287 * @param payload
289 private synchronized void cursorVisibility(String payload) {
290 if (payload.equals("true")) {
291 mDocumentOverlay.showCursor();
292 if (mState != OverlayState.SELECTION) {
293 mDocumentOverlay.showHandle(SelectionHandle.HandleType.MIDDLE);
295 } else if (payload.equals("false")) {
296 mDocumentOverlay.hideCursor();
297 mDocumentOverlay.hideHandle(SelectionHandle.HandleType.MIDDLE);
302 * Handles the graphic selection change message
304 * @param payload
306 private void graphicSelection(String payload) {
307 if (payload.isEmpty() || payload.equals("EMPTY")) {
308 if (mState == OverlayState.GRAPHIC_SELECTION) {
309 changeStateTo(OverlayState.TRANSITION);
311 } else {
312 RectF rectangle = convertPayloadToRectangle(payload);
313 mDocumentOverlay.changeGraphicSelection(rectangle);
314 if (mState != OverlayState.GRAPHIC_SELECTION) {
315 changeStateTo(OverlayState.TRANSITION);
317 changeStateTo(OverlayState.GRAPHIC_SELECTION);
322 * Trigger a transition to a new overlay state.
324 * @param next - new state to transition to
326 public synchronized void changeStateTo(OverlayState next) {
327 changeState(mState, next);
331 * Executes a transition from old overlay state to a new overlay state.
333 * @param previous - old state
334 * @param next - new state
336 private synchronized void changeState(OverlayState previous, OverlayState next) {
337 mState = next;
338 handleGeneralChangeState(previous, next);
339 switch (next) {
340 case CURSOR:
341 handleCursorState(previous);
342 break;
343 case SELECTION:
344 handleSelectionState(previous);
345 break;
346 case GRAPHIC_SELECTION:
347 handleGraphicSelectionState(previous);
348 break;
349 case TRANSITION:
350 handleTransitionState(previous);
351 break;
352 case NONE:
353 handleNoneState(previous);
354 break;
359 * Handle a general transition - executed for all transitions.
361 private void handleGeneralChangeState(OverlayState previous, OverlayState next) {
362 if (previous == OverlayState.NONE) {
363 LOKitShell.getToolbarController().switchToEditMode();
364 } else if (next == OverlayState.NONE) {
365 LOKitShell.getToolbarController().switchToViewMode();
370 * Handle a transition to OverlayState.NONE state.
372 private void handleNoneState(OverlayState previous) {
373 if (previous == OverlayState.NONE) {
374 return;
377 // Just hide everything
378 mDocumentOverlay.hideHandle(SelectionHandle.HandleType.START);
379 mDocumentOverlay.hideHandle(SelectionHandle.HandleType.END);
380 mDocumentOverlay.hideHandle(SelectionHandle.HandleType.MIDDLE);
381 mDocumentOverlay.hideSelections();
382 mDocumentOverlay.hideCursor();
383 mDocumentOverlay.hideGraphicSelection();
384 LibreOfficeMainActivity.mAppContext.hideSoftKeyboard();
388 * Handle a transition to OverlayState.SELECTION state.
390 private void handleSelectionState(OverlayState previous) {
391 mDocumentOverlay.showHandle(SelectionHandle.HandleType.START);
392 mDocumentOverlay.showHandle(SelectionHandle.HandleType.END);
393 mDocumentOverlay.showSelections();
397 * Handle a transition to OverlayState.CURSOR state.
399 private void handleCursorState(OverlayState previous) {
400 LibreOfficeMainActivity.mAppContext.showSoftKeyboardOrFormattingToolbar();
401 if (previous == OverlayState.TRANSITION) {
402 mDocumentOverlay.showHandle(SelectionHandle.HandleType.MIDDLE);
403 mDocumentOverlay.showCursor();
408 * Handle a transition to OverlayState.TRANSITION state.
410 private void handleTransitionState(OverlayState previous) {
411 if (previous == OverlayState.SELECTION) {
412 mDocumentOverlay.hideHandle(SelectionHandle.HandleType.START);
413 mDocumentOverlay.hideHandle(SelectionHandle.HandleType.END);
414 mDocumentOverlay.hideSelections();
415 } else if (previous == OverlayState.CURSOR) {
416 mDocumentOverlay.hideHandle(SelectionHandle.HandleType.MIDDLE);
417 } else if (previous == OverlayState.GRAPHIC_SELECTION) {
418 mDocumentOverlay.hideGraphicSelection();
423 * Handle a transition to OverlayState.GRAPHIC_SELECTION state.
425 private void handleGraphicSelectionState(OverlayState previous) {
426 mDocumentOverlay.showGraphicSelection();
427 LibreOfficeMainActivity.mAppContext.hideSoftKeyboard();
431 * The current state the overlay is in.
433 public OverlayState getCurrentState() {
434 return mState;
438 * A key event happened (i.e. user started typing).
440 public void keyEvent() {
441 mKeyEvent = true;
445 * The states the overlay.
447 public enum OverlayState {
449 * State where the overlay is empty
451 NONE,
453 * In-between state where we need to transition to a new overlay state.
454 * In this state we properly disable the older state and wait to transition
455 * to a new state triggered by an invalidation.
457 TRANSITION,
459 * State where we operate with the cursor.
461 CURSOR,
463 * State where we operate the graphic selection.
465 GRAPHIC_SELECTION,
467 * State where we operate the text selection.
469 SELECTION
473 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */