Avoid potential negative array index access to cached text.
[LibreOffice.git] / android / source / src / java / org / libreoffice / InvalidationHandler.java
blobc48127cce67f0f9e076cbafeabd83e6d8276469e
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;
24 /**
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) {
38 mContext = context;
39 mDocumentOverlay = mContext.getDocumentOverlay();
40 mLayerClient = mContext.getLayerClient();
41 mState = OverlayState.NONE;
44 /**
45 * Processes callback message
47 * @param messageID - ID of the message
48 * @param payload - additional invalidation message payload
50 @Override
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)
62 return;
64 switch (messageID) {
65 case Document.CALLBACK_INVALIDATE_TILES:
66 invalidateTiles(payload);
67 break;
68 case Document.CALLBACK_UNO_COMMAND_RESULT:
69 unoCommandResult(payload);
70 break;
71 case Document.CALLBACK_INVALIDATE_VISIBLE_CURSOR:
72 invalidateCursor(payload);
73 break;
74 case Document.CALLBACK_TEXT_SELECTION:
75 textSelection(payload);
76 break;
77 case Document.CALLBACK_TEXT_SELECTION_START:
78 textSelectionStart(payload);
79 break;
80 case Document.CALLBACK_TEXT_SELECTION_END:
81 textSelectionEnd(payload);
82 break;
83 case Document.CALLBACK_CURSOR_VISIBLE:
84 cursorVisibility(payload);
85 break;
86 case Document.CALLBACK_GRAPHIC_SELECTION:
87 graphicSelection(payload);
88 break;
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);
96 break;
97 case Document.CALLBACK_STATE_CHANGED:
98 stateChanged(payload);
99 break;
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);
110 break;
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.
114 break;
115 case Document.CALLBACK_CELL_CURSOR:
116 invalidateCellCursor(payload);
117 break;
118 case Document.CALLBACK_SC_FOLLOW_JUMP:
119 jumpToCell(payload);
120 break;
121 case Document.CALLBACK_INVALIDATE_HEADER:
122 invalidateHeader();
123 break;
124 case Document.CALLBACK_CELL_ADDRESS:
125 cellAddress(payload);
126 break;
127 case Document.CALLBACK_CELL_FORMULA:
128 cellFormula(payload);
129 break;
130 case Document.CALLBACK_DOCUMENT_PASSWORD:
131 documentPassword();
132 break;
133 case Document.CALLBACK_DOCUMENT_SIZE_CHANGED:
134 pageSizeChanged(payload);
135 default:
137 Log.d(LOGTAG, "LOK_CALLBACK uncaught: " + messageID + " : " + payload);
141 private void unoCommandResult(String payload) {
142 try {
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() {
152 @Override
153 public void run() {
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() {
163 @Override
164 public void run() {
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){
173 e.printStackTrace();
177 private void cellFormula(final String payload) {
178 LOKitShell.getMainHandler().post(new Runnable() {
179 @Override
180 public void run() {
181 ((EditText)mContext.findViewById(R.id.calc_formula)).setText(payload);
186 private void cellAddress(final String payload) {
187 LOKitShell.getMainHandler().post(new Runnable() {
188 @Override
189 public void run() {
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) {
203 try {
204 this.wait();
205 } catch (InterruptedException e) {
206 e.printStackTrace();
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
232 * @param payload
234 private void searchResultSelection(String payload) {
235 RectF selectionRectangle = null;
236 try {
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) {
244 e.printStackTrace();
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
257 * accordingly.
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)) {
264 return;
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);
311 return;
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);
317 return;
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));
360 } else {
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")) {
375 return null;
378 String[] coordinates = payloadWithoutWhitespace.split(",");
380 if (coordinates.length != 4) {
381 return null;
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")) {
396 return null;
399 String[] coordinates = payloadWithoutWhitespace.split(",");
401 if (coordinates.length != 6 ) {
402 return null;
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 ) {
415 return null;
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);
425 return new RectF(
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);
451 return rectangles;
455 * Handles the tile invalidation message
457 * @param payload
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
469 * @param payload
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);
481 if (mKeyEvent) {
482 moveViewportToMakeCursorVisible(cursorRectangle);
483 mKeyEvent = false;
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)) {
498 return;
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
522 * @param payload
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
534 * @param payload
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
546 * @param payload
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);
558 } else {
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
576 * @param payload
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
593 * @param payload
595 private void graphicSelection(String payload) {
596 if (payload.isEmpty() || payload.equals("EMPTY")) {
597 if (mState == OverlayState.GRAPHIC_SELECTION) {
598 changeStateTo(OverlayState.TRANSITION);
600 } else {
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) {
626 mState = next;
627 handleGeneralChangeState(previous, next);
628 switch (next) {
629 case CURSOR:
630 handleCursorState(previous);
631 break;
632 case SELECTION:
633 handleSelectionState(previous);
634 break;
635 case GRAPHIC_SELECTION:
636 handleGraphicSelectionState(previous);
637 break;
638 case TRANSITION:
639 handleTransitionState(previous);
640 break;
641 case NONE:
642 handleNoneState(previous);
643 break;
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) {
665 return;
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) {
702 switch (previous) {
703 case SELECTION:
704 mDocumentOverlay.hideHandle(SelectionHandle.HandleType.START);
705 mDocumentOverlay.hideHandle(SelectionHandle.HandleType.END);
706 mDocumentOverlay.hideSelections();
707 break;
708 case CURSOR:
709 mDocumentOverlay.hideHandle(SelectionHandle.HandleType.MIDDLE);
710 break;
711 case GRAPHIC_SELECTION:
712 mDocumentOverlay.hideGraphicSelection();
713 break;
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() {
729 return mState;
733 * A key event happened (i.e. user started typing).
735 public void keyEvent() {
736 mKeyEvent = true;
740 * The states the overlay.
742 public enum OverlayState {
744 * State where the overlay is empty
746 NONE,
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.
752 TRANSITION,
754 * State where we operate with the cursor.
756 CURSOR,
758 * State where we operate the graphic selection.
760 GRAPHIC_SELECTION,
762 * State where we operate the text selection.
764 SELECTION
768 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */