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
;
8 import android
.widget
.EditText
;
9 import android
.widget
.Toast
;
11 import org
.json
.JSONArray
;
12 import org
.json
.JSONException
;
13 import org
.json
.JSONObject
;
14 import org
.libreoffice
.canvas
.SelectionHandle
;
15 import org
.libreoffice
.kit
.Document
;
16 import org
.libreoffice
.kit
.Office
;
17 import org
.libreoffice
.overlay
.DocumentOverlay
;
18 import org
.mozilla
.gecko
.gfx
.GeckoLayerClient
;
20 import java
.util
.ArrayList
;
21 import java
.util
.Collections
;
22 import java
.util
.List
;
25 * Parses (interprets) and handles invalidation messages from LibreOffice.
27 public class InvalidationHandler
implements Document
.MessageCallback
, Office
.MessageCallback
{
28 private static final String LOGTAG
= InvalidationHandler
.class.getSimpleName();
29 private final DocumentOverlay mDocumentOverlay
;
30 private final GeckoLayerClient mLayerClient
;
31 private OverlayState mState
;
32 private boolean mKeyEvent
= false;
33 private final LibreOfficeMainActivity mContext
;
35 private int currentTotalPageNumber
= 0; // total page number of the current document
37 public InvalidationHandler(LibreOfficeMainActivity context
) {
39 mDocumentOverlay
= mContext
.getDocumentOverlay();
40 mLayerClient
= mContext
.getLayerClient();
41 mState
= OverlayState
.NONE
;
45 * Processes callback message
47 * @param messageID - ID of the message
48 * @param payload - additional invalidation message payload
51 public void messageRetrieved(int messageID
, String payload
) {
52 if (!LOKitShell
.isEditingEnabled()) {
53 // enable handling of hyperlinks and search result even in the Viewer
54 if (messageID
!= Document
.CALLBACK_INVALIDATE_TILES
55 && messageID
!= Document
.CALLBACK_DOCUMENT_PASSWORD
56 && messageID
!= Document
.CALLBACK_HYPERLINK_CLICKED
57 && messageID
!= Document
.CALLBACK_SEARCH_RESULT_SELECTION
58 && messageID
!= Document
.CALLBACK_SC_FOLLOW_JUMP
59 && messageID
!= Document
.CALLBACK_TEXT_SELECTION
60 && messageID
!= Document
.CALLBACK_TEXT_SELECTION_START
61 && messageID
!= Document
.CALLBACK_TEXT_SELECTION_END
)
65 case Document
.CALLBACK_INVALIDATE_TILES
:
66 invalidateTiles(payload
);
68 case Document
.CALLBACK_UNO_COMMAND_RESULT
:
69 unoCommandResult(payload
);
71 case Document
.CALLBACK_INVALIDATE_VISIBLE_CURSOR
:
72 invalidateCursor(payload
);
74 case Document
.CALLBACK_TEXT_SELECTION
:
75 textSelection(payload
);
77 case Document
.CALLBACK_TEXT_SELECTION_START
:
78 textSelectionStart(payload
);
80 case Document
.CALLBACK_TEXT_SELECTION_END
:
81 textSelectionEnd(payload
);
83 case Document
.CALLBACK_CURSOR_VISIBLE
:
84 cursorVisibility(payload
);
86 case Document
.CALLBACK_GRAPHIC_SELECTION
:
87 graphicSelection(payload
);
89 case Document
.CALLBACK_HYPERLINK_CLICKED
:
90 if (!payload
.startsWith("http://") && !payload
.startsWith("https://")) {
91 payload
= "http://" + payload
;
93 Intent urlIntent
= new Intent(Intent
.ACTION_VIEW
);
94 urlIntent
.setData(Uri
.parse(payload
));
95 mContext
.startActivity(urlIntent
);
97 case Document
.CALLBACK_STATE_CHANGED
:
98 stateChanged(payload
);
100 case Document
.CALLBACK_SEARCH_RESULT_SELECTION
:
101 searchResultSelection(payload
);
102 // when doing a search, CALLBACK_SEARCH_RESULT_SELECTION is called in addition
103 // to the CALLBACK_TEXT_SELECTION{,_START,_END} callbacks and the handling of
104 // the previous 3 makes the cursor shown in addition to the selection rectangle,
105 // so hide the cursor again to just show the selection rectangle for the search result
106 mDocumentOverlay
.hideCursor();
107 mDocumentOverlay
.hideHandle(SelectionHandle
.HandleType
.MIDDLE
);
108 mDocumentOverlay
.hideHandle(SelectionHandle
.HandleType
.START
);
109 mDocumentOverlay
.hideHandle(SelectionHandle
.HandleType
.END
);
111 case Document
.CALLBACK_SEARCH_NOT_FOUND
:
112 Log
.d(LOGTAG
, "LOK_CALLBACK: Search not found.");
113 // this callback is never caught. Hope someone fix this.
115 case Document
.CALLBACK_CELL_CURSOR
:
116 invalidateCellCursor(payload
);
118 case Document
.CALLBACK_SC_FOLLOW_JUMP
:
121 case Document
.CALLBACK_INVALIDATE_HEADER
:
124 case Document
.CALLBACK_CELL_ADDRESS
:
125 cellAddress(payload
);
127 case Document
.CALLBACK_CELL_FORMULA
:
128 cellFormula(payload
);
130 case Document
.CALLBACK_DOCUMENT_PASSWORD
:
133 case Document
.CALLBACK_DOCUMENT_SIZE_CHANGED
:
134 pageSizeChanged(payload
);
137 Log
.d(LOGTAG
, "LOK_CALLBACK uncaught: " + messageID
+ " : " + payload
);
141 private void unoCommandResult(String payload
) {
143 JSONObject payloadObject
= new JSONObject(payload
);
144 if (payloadObject
.getString("commandName").equals(".uno:Save")) {
145 if (payloadObject
.getString("success").equals("true")) {
146 mContext
.saveFileToOriginalSource();
148 }else if(payloadObject
.getString("commandName").equals(".uno:Name") ||
149 payloadObject
.getString("commandName").equals(".uno:RenamePage")){
150 //success returns false even though its true for some reason,
151 LOKitShell
.getMainHandler().post(new Runnable() {
154 mContext
.getTileProvider().resetParts();
155 mContext
.getDocumentPartViewListAdapter().notifyDataSetChanged();
156 LibreOfficeMainActivity
.setDocumentChanged(true);
157 Toast
.makeText(mContext
, mContext
.getString(R
.string
.part_name_changed
), Toast
.LENGTH_SHORT
).show();
160 } else if(payloadObject
.getString("commandName").equals(".uno:Remove") ||
161 payloadObject
.getString("commandName").equals(".uno:DeletePage") ) {
162 LOKitShell
.getMainHandler().post(new Runnable() {
165 mContext
.getTileProvider().resetParts();
166 mContext
.getDocumentPartViewListAdapter().notifyDataSetChanged();
167 LibreOfficeMainActivity
.setDocumentChanged(true);
168 Toast
.makeText(mContext
, mContext
.getString(R
.string
.part_deleted
), Toast
.LENGTH_SHORT
).show();
172 }catch(JSONException e
){
177 private void cellFormula(final String payload
) {
178 LOKitShell
.getMainHandler().post(new Runnable() {
181 ((EditText
)mContext
.findViewById(R
.id
.calc_formula
)).setText(payload
);
186 private void cellAddress(final String payload
) {
187 LOKitShell
.getMainHandler().post(new Runnable() {
190 ((EditText
)mContext
.findViewById(R
.id
.calc_address
)).setText(payload
);
195 private void invalidateHeader() {
196 LOKitShell
.sendEvent(new LOEvent(LOEvent
.UPDATE_CALC_HEADERS
));
199 private void documentPassword() {
200 mContext
.setPasswordProtected(true);
201 mContext
.promptForPassword();
202 synchronized (this) {
205 } catch (InterruptedException e
) {
209 mContext
.setPassword();
212 private void invalidateCellCursor(String payload
) {
213 RectF cellCursorRect
= convertPayloadToRectangle(payload
);
215 if (cellCursorRect
!= null) {
216 mDocumentOverlay
.showCellSelection(cellCursorRect
);
217 moveViewportToMakeSelectionVisible(cellCursorRect
);
221 private void jumpToCell(String payload
) {
222 RectF cellCursorRect
= convertPayloadCellToRectangle(payload
);
224 if (cellCursorRect
!= null) {
225 moveViewportToMakeSelectionVisible(cellCursorRect
);
230 * Handles the search result selection message, which is a JSONObject
234 private void searchResultSelection(String payload
) {
235 RectF selectionRectangle
= null;
237 JSONObject collectiveResult
= new JSONObject(payload
);
238 JSONArray searchResult
= collectiveResult
.getJSONArray("searchResultSelection");
239 if (searchResult
.length() == 1) {
240 String rectangle
= searchResult
.getJSONObject(0).getString("rectangles");
241 selectionRectangle
= convertPayloadToRectangle(rectangle
);
243 } catch (JSONException e
) {
246 if (selectionRectangle
!= null) {
247 moveViewportToMakeSelectionVisible(selectionRectangle
);
252 * Move the viewport to show the selection. The selection will appear at the
253 * viewport position depending on where the selection is relative to the
254 * viewport (either selection is above, below, on left or right). The difference
255 * between this method and moveViewportToMakeCursorVisible() is that this method
256 * takes into account the width and height of the selection and zooms out
259 * @param selectionRectangle - selection position on the document
261 public void moveViewportToMakeSelectionVisible(RectF selectionRectangle
) {
262 RectF moveToRect
= mLayerClient
.getViewportMetrics().getCssViewport();
263 if (moveToRect
.contains(selectionRectangle
)) {
267 float newLeft
= moveToRect
.left
;
268 float newTop
= moveToRect
.top
;
270 // if selection rectangle is wider or taller than current viewport, we need to zoom out
271 float oldZoom
= mLayerClient
.getViewportMetrics().getZoomFactor();
272 float widthRatio
= 1f
;
273 float heightRatio
= 1f
;
274 if (moveToRect
.width() < selectionRectangle
.width()) {
275 widthRatio
= selectionRectangle
.width() / moveToRect
.width() / 0.85f
; // 0.85f gives some margin (must < 0.9)
277 if (moveToRect
.height() < selectionRectangle
.height()) {
278 heightRatio
= selectionRectangle
.height() / moveToRect
.height() / 0.45f
; // 0.45f gives some margin (must < 0.5)
280 float newZoom
= widthRatio
> heightRatio ? oldZoom
/widthRatio
: oldZoom
/heightRatio
;
282 // if selection is out of viewport we need to adjust accordingly
283 if (selectionRectangle
.right
< moveToRect
.left
|| selectionRectangle
.left
< moveToRect
.left
) {
284 newLeft
= selectionRectangle
.left
- (moveToRect
.width() * 0.1f
) * oldZoom
/ newZoom
; // 0.1f gives left margin
285 } else if (selectionRectangle
.right
> moveToRect
.right
|| selectionRectangle
.left
> moveToRect
.right
) {
286 newLeft
= selectionRectangle
.right
- (moveToRect
.width() * 0.9f
) * oldZoom
/ newZoom
; // 0.9f gives right margin
289 if (selectionRectangle
.top
< moveToRect
.top
|| selectionRectangle
.bottom
< moveToRect
.top
) {
290 newTop
= selectionRectangle
.top
- (moveToRect
.height() * 0.1f
) * oldZoom
/ newZoom
; // 0.1f gives top margin
291 } else if (selectionRectangle
.bottom
> moveToRect
.bottom
|| selectionRectangle
.top
> moveToRect
.bottom
){
292 newTop
= selectionRectangle
.bottom
- (moveToRect
.height() * 0.5f
) * oldZoom
/ newZoom
; // 0.5 f gives bottom margin
295 LOKitShell
.moveViewportTo(mContext
, new PointF(newLeft
, newTop
), newZoom
);
298 private void pageSizeChanged(String payload
){
299 if(mContext
.getTileProvider().isTextDocument()){
300 String
[] bounds
= payload
.split(",");
301 int pageWidth
= Integer
.parseInt(bounds
[0]);
302 int pageHeight
= Integer
.parseInt(bounds
[1].trim());
303 LOKitShell
.sendEvent(new LOEvent(LOEvent
.PAGE_SIZE_CHANGED
, pageWidth
, pageHeight
));
307 private void stateChanged(String payload
) {
308 String
[] parts
= payload
.split("=");
309 if (parts
.length
< 2) {
310 Log
.e(LOGTAG
, "LOK_CALLBACK_STATE_CHANGED unexpected payload: " + payload
);
313 final String value
= parts
[1];
314 boolean pressed
= Boolean
.parseBoolean(value
);
315 if (!mContext
.getTileProvider().isReady()) {
316 Log
.w(LOGTAG
, "tile provider not ready, ignoring payload "+payload
);
319 if (parts
[0].equals(".uno:Bold")) {
320 mContext
.getFormattingController().onToggleStateChanged(Document
.BOLD
, pressed
);
321 } else if (parts
[0].equals(".uno:Italic")) {
322 mContext
.getFormattingController().onToggleStateChanged(Document
.ITALIC
, pressed
);
323 } else if (parts
[0].equals(".uno:Underline")) {
324 mContext
.getFormattingController().onToggleStateChanged(Document
.UNDERLINE
, pressed
);
325 } else if (parts
[0].equals(".uno:Strikeout")) {
326 mContext
.getFormattingController().onToggleStateChanged(Document
.STRIKEOUT
, pressed
);
327 } else if (parts
[0].equals(".uno:CharFontName")) {
328 mContext
.getFontController().selectFont(value
);
329 } else if (parts
[0].equals(".uno:FontHeight")) {
330 mContext
.getFontController().selectFontSize(value
);
331 } else if (parts
[0].equals(".uno:LeftPara")) {
332 mContext
.getFormattingController().onToggleStateChanged(Document
.ALIGN_LEFT
, pressed
);
333 } else if (parts
[0].equals(".uno:CenterPara")) {
334 mContext
.getFormattingController().onToggleStateChanged(Document
.ALIGN_CENTER
, pressed
);
335 } else if (parts
[0].equals(".uno:RightPara")) {
336 mContext
.getFormattingController().onToggleStateChanged(Document
.ALIGN_RIGHT
, pressed
);
337 } else if (parts
[0].equals(".uno:JustifyPara")) {
338 mContext
.getFormattingController().onToggleStateChanged(Document
.ALIGN_JUSTIFY
, pressed
);
339 } else if (parts
[0].equals(".uno:DefaultBullet")) {
340 mContext
.getFormattingController().onToggleStateChanged(Document
.BULLET_LIST
, pressed
);
341 } else if (parts
[0].equals(".uno:DefaultNumbering")) {
342 mContext
.getFormattingController().onToggleStateChanged(Document
.NUMBERED_LIST
, pressed
);
343 } else if (parts
[0].equals(".uno:Color")) {
344 mContext
.getFontController().colorPaletteListener
.updateColorPickerPosition(Integer
.parseInt(value
));
345 } else if (mContext
.getTileProvider().isTextDocument() && (parts
[0].equals(".uno:BackColor") || parts
[0].equals(".uno:CharBackColor"))) {
346 mContext
.getFontController().backColorPaletteListener
.updateColorPickerPosition(Integer
.parseInt(value
));
347 } else if (mContext
.getTileProvider().isPresentation() && parts
[0].equals(".uno:CharBackColor")) {
348 mContext
.getFontController().backColorPaletteListener
.updateColorPickerPosition(Integer
.parseInt(value
));
349 } else if (mContext
.getTileProvider().isSpreadsheet() && parts
[0].equals(".uno:BackgroundColor")) {
350 mContext
.getFontController().backColorPaletteListener
.updateColorPickerPosition(Integer
.parseInt(value
));
351 } else if (parts
[0].equals(".uno:StatePageNumber")) {
352 // get the total page number and compare to the current value and update accordingly
353 String
[] splitStrings
= parts
[1].split(" ");
354 int totalPageNumber
= Integer
.valueOf(splitStrings
[splitStrings
.length
- 1]);
355 if (totalPageNumber
!= currentTotalPageNumber
) {
356 currentTotalPageNumber
= totalPageNumber
;
357 // update part page rectangles stored in DocumentOverlayView object
358 LOKitShell
.sendEvent(new LOEvent(LOEvent
.UPDATE_PART_PAGE_RECT
));
361 Log
.d(LOGTAG
, "LOK_CALLBACK_STATE_CHANGED type uncatched: " + payload
);
366 * Parses the payload text with rectangle coordinates and converts to rectangle in pixel coordinates
368 * @param payload - invalidation message payload text
369 * @return rectangle in pixel coordinates
371 public RectF
convertPayloadToRectangle(String payload
) {
372 String payloadWithoutWhitespace
= payload
.replaceAll("\\s", ""); // remove all whitespace from the string
374 if (payloadWithoutWhitespace
.isEmpty() || payloadWithoutWhitespace
.equals("EMPTY")) {
378 String
[] coordinates
= payloadWithoutWhitespace
.split(",");
380 if (coordinates
.length
!= 4) {
383 return convertPayloadToRectangle(coordinates
);
387 * Parses the payload text with rectangle coordinates and converts to rectangle in pixel coordinates
389 * @param payload - invalidation message payload text
390 * @return rectangle in pixel coordinates
392 public RectF
convertPayloadCellToRectangle(String payload
) {
393 String payloadWithoutWhitespace
= payload
.replaceAll("\\s", ""); // remove all whitespace from the string
395 if (payloadWithoutWhitespace
.isEmpty() || payloadWithoutWhitespace
.equals("EMPTY")) {
399 String
[] coordinates
= payloadWithoutWhitespace
.split(",");
401 if (coordinates
.length
!= 6 ) {
404 return convertPayloadToRectangle(coordinates
);
408 * Converts rectangle coordinates to rectangle in pixel coordinates
410 * @param coordinates - the first four items defines the rectangle
411 * @return rectangle in pixel coordinates
413 public RectF
convertPayloadToRectangle(String
[] coordinates
) {
414 if (coordinates
.length
< 4 ) {
418 int x
= Integer
.decode(coordinates
[0]);
419 int y
= Integer
.decode(coordinates
[1]);
420 int width
= Integer
.decode(coordinates
[2]);
421 int height
= Integer
.decode(coordinates
[3]);
423 float dpi
= LOKitShell
.getDpi(mContext
);
426 LOKitTileProvider
.twipToPixel(x
, dpi
),
427 LOKitTileProvider
.twipToPixel(y
, dpi
),
428 LOKitTileProvider
.twipToPixel(x
+ width
, dpi
),
429 LOKitTileProvider
.twipToPixel(y
+ height
, dpi
)
434 * Parses the payload text with more rectangles (separated by ';') and converts to a list of rectangles.
436 * @param payload - invalidation message payload text
437 * @return list of rectangles
439 public List
<RectF
> convertPayloadToRectangles(String payload
) {
440 List
<RectF
> rectangles
= new ArrayList
<RectF
>();
441 String
[] rectangleArray
= payload
.split(";");
443 for (String coordinates
: rectangleArray
) {
444 RectF rectangle
= convertPayloadToRectangle(coordinates
);
445 if (rectangle
!= null) {
446 rectangles
.add(rectangle
);
455 * Handles the tile invalidation message
459 private void invalidateTiles(String payload
) {
460 RectF rectangle
= convertPayloadToRectangle(payload
);
461 if (rectangle
!= null) {
462 LOKitShell
.sendTileInvalidationRequest(rectangle
);
467 * Handles the cursor invalidation message
471 private synchronized void invalidateCursor(String payload
) {
472 RectF cursorRectangle
= convertPayloadToRectangle(payload
);
473 if (cursorRectangle
!= null) {
474 mDocumentOverlay
.positionCursor(cursorRectangle
);
475 mDocumentOverlay
.positionHandle(SelectionHandle
.HandleType
.MIDDLE
, cursorRectangle
);
477 if (mState
== OverlayState
.TRANSITION
|| mState
== OverlayState
.CURSOR
) {
478 changeStateTo(OverlayState
.CURSOR
);
482 moveViewportToMakeCursorVisible(cursorRectangle
);
489 * Move the viewport to show the cursor. The cursor will appear at the
490 * viewport position depending on where the cursor is relative to the
491 * viewport (either cursor is above, below, on left or right).
493 * @param cursorRectangle - cursor position on the document
495 public void moveViewportToMakeCursorVisible(RectF cursorRectangle
) {
496 RectF moveToRect
= mLayerClient
.getViewportMetrics().getCssViewport();
497 if (moveToRect
.contains(cursorRectangle
)) {
501 float newLeft
= moveToRect
.left
;
502 float newTop
= moveToRect
.top
;
504 if (cursorRectangle
.right
< moveToRect
.left
|| cursorRectangle
.left
< moveToRect
.left
) {
505 newLeft
= cursorRectangle
.left
- (moveToRect
.width() * 0.1f
);
506 } else if (cursorRectangle
.right
> moveToRect
.right
|| cursorRectangle
.left
> moveToRect
.right
) {
507 newLeft
= cursorRectangle
.right
- (moveToRect
.width() * 0.9f
);
510 if (cursorRectangle
.top
< moveToRect
.top
|| cursorRectangle
.bottom
< moveToRect
.top
) {
511 newTop
= cursorRectangle
.top
- (moveToRect
.height() * 0.1f
);
512 } else if (cursorRectangle
.bottom
> moveToRect
.bottom
|| cursorRectangle
.top
> moveToRect
.bottom
) {
513 newTop
= cursorRectangle
.bottom
- (moveToRect
.height() / 2.0f
);
516 LOKitShell
.moveViewportTo(mContext
, new PointF(newLeft
, newTop
), null);
520 * Handles the text selection start message
524 private synchronized void textSelectionStart(String payload
) {
525 RectF selectionRect
= convertPayloadToRectangle(payload
);
526 if (selectionRect
!= null) {
527 mDocumentOverlay
.positionHandle(SelectionHandle
.HandleType
.START
, selectionRect
);
532 * Handles the text selection end message
536 private synchronized void textSelectionEnd(String payload
) {
537 RectF selectionRect
= convertPayloadToRectangle(payload
);
538 if (selectionRect
!= null) {
539 mDocumentOverlay
.positionHandle(SelectionHandle
.HandleType
.END
, selectionRect
);
544 * Handles the text selection message
548 private synchronized void textSelection(String payload
) {
549 if (payload
.isEmpty() || payload
.equals("EMPTY")) {
550 if (mState
== OverlayState
.SELECTION
) {
551 changeStateTo(OverlayState
.TRANSITION
);
553 mDocumentOverlay
.changeSelections(Collections
.<RectF
>emptyList());
554 if (mContext
.getTileProvider().isSpreadsheet()) {
555 mDocumentOverlay
.showHeaderSelection(null);
557 mContext
.getToolbarController().showHideClipboardCutAndCopy(false);
559 List
<RectF
> rectangles
= convertPayloadToRectangles(payload
);
560 if (mState
!= OverlayState
.SELECTION
) {
561 changeStateTo(OverlayState
.TRANSITION
);
563 changeStateTo(OverlayState
.SELECTION
);
564 mDocumentOverlay
.changeSelections(rectangles
);
565 if (mContext
.getTileProvider().isSpreadsheet()) {
566 mDocumentOverlay
.showHeaderSelection(rectangles
.get(0));
568 String selectedText
= mContext
.getTileProvider().getTextSelection("");
569 mContext
.getToolbarController().showClipboardActions(selectedText
);
574 * Handles the cursor visibility message
578 private synchronized void cursorVisibility(String payload
) {
579 if (payload
.equals("true")) {
580 mDocumentOverlay
.showCursor();
581 if (mState
!= OverlayState
.SELECTION
) {
582 mDocumentOverlay
.showHandle(SelectionHandle
.HandleType
.MIDDLE
);
584 } else if (payload
.equals("false")) {
585 mDocumentOverlay
.hideCursor();
586 mDocumentOverlay
.hideHandle(SelectionHandle
.HandleType
.MIDDLE
);
591 * Handles the graphic selection change message
595 private void graphicSelection(String payload
) {
596 if (payload
.isEmpty() || payload
.equals("EMPTY")) {
597 if (mState
== OverlayState
.GRAPHIC_SELECTION
) {
598 changeStateTo(OverlayState
.TRANSITION
);
601 RectF rectangle
= convertPayloadToRectangle(payload
);
602 mDocumentOverlay
.changeGraphicSelection(rectangle
);
603 if (mState
!= OverlayState
.GRAPHIC_SELECTION
) {
604 changeStateTo(OverlayState
.TRANSITION
);
606 changeStateTo(OverlayState
.GRAPHIC_SELECTION
);
611 * Trigger a transition to a new overlay state.
613 * @param next - new state to transition to
615 public synchronized void changeStateTo(OverlayState next
) {
616 changeState(mState
, next
);
620 * Executes a transition from old overlay state to a new overlay state.
622 * @param previous - old state
623 * @param next - new state
625 private synchronized void changeState(OverlayState previous
, OverlayState next
) {
627 handleGeneralChangeState(previous
, next
);
630 handleCursorState(previous
);
633 handleSelectionState(previous
);
635 case GRAPHIC_SELECTION
:
636 handleGraphicSelectionState(previous
);
639 handleTransitionState(previous
);
642 handleNoneState(previous
);
648 * Handle a general transition - executed for all transitions.
650 private void handleGeneralChangeState(OverlayState previous
, OverlayState next
) {
651 if (previous
== OverlayState
.NONE
&&
652 !mContext
.getToolbarController().getEditModeStatus()) {
653 mContext
.getToolbarController().switchToEditMode();
654 } else if (next
== OverlayState
.NONE
&&
655 mContext
.getToolbarController().getEditModeStatus()) {
656 mContext
.getToolbarController().switchToViewMode();
661 * Handle a transition to OverlayState.NONE state.
663 private void handleNoneState(OverlayState previous
) {
664 if (previous
== OverlayState
.NONE
) {
668 // Just hide everything
669 mDocumentOverlay
.hideHandle(SelectionHandle
.HandleType
.START
);
670 mDocumentOverlay
.hideHandle(SelectionHandle
.HandleType
.END
);
671 mDocumentOverlay
.hideHandle(SelectionHandle
.HandleType
.MIDDLE
);
672 mDocumentOverlay
.hideSelections();
673 mDocumentOverlay
.hideCursor();
674 mDocumentOverlay
.hideGraphicSelection();
675 mContext
.hideSoftKeyboard();
679 * Handle a transition to OverlayState.SELECTION state.
681 private void handleSelectionState(OverlayState previous
) {
682 mDocumentOverlay
.showHandle(SelectionHandle
.HandleType
.START
);
683 mDocumentOverlay
.showHandle(SelectionHandle
.HandleType
.END
);
684 mDocumentOverlay
.showSelections();
688 * Handle a transition to OverlayState.CURSOR state.
690 private void handleCursorState(OverlayState previous
) {
691 mContext
.showSoftKeyboardOrFormattingToolbar();
692 if (previous
== OverlayState
.TRANSITION
) {
693 mDocumentOverlay
.showHandle(SelectionHandle
.HandleType
.MIDDLE
);
694 mDocumentOverlay
.showCursor();
699 * Handle a transition to OverlayState.TRANSITION state.
701 private void handleTransitionState(OverlayState previous
) {
704 mDocumentOverlay
.hideHandle(SelectionHandle
.HandleType
.START
);
705 mDocumentOverlay
.hideHandle(SelectionHandle
.HandleType
.END
);
706 mDocumentOverlay
.hideSelections();
709 mDocumentOverlay
.hideHandle(SelectionHandle
.HandleType
.MIDDLE
);
711 case GRAPHIC_SELECTION
:
712 mDocumentOverlay
.hideGraphicSelection();
718 * Handle a transition to OverlayState.GRAPHIC_SELECTION state.
720 private void handleGraphicSelectionState(OverlayState previous
) {
721 mDocumentOverlay
.showGraphicSelection();
722 mContext
.hideSoftKeyboard();
726 * The current state the overlay is in.
728 public OverlayState
getCurrentState() {
733 * A key event happened (i.e. user started typing).
735 public void keyEvent() {
740 * The states the overlay.
742 public enum OverlayState
{
744 * State where the overlay is empty
748 * In-between state where we need to transition to a new overlay state.
749 * In this state we properly disable the older state and wait to transition
750 * to a new state triggered by an invalidation.
754 * State where we operate with the cursor.
758 * State where we operate the graphic selection.
762 * State where we operate the text selection.
768 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */