Avoid potential negative array index access to cached text.
[LibreOffice.git] / android / source / src / java / org / libreoffice / overlay / DocumentOverlayView.java
blob086108cd903f94bd931275df818d978ea377f3ef
1 /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
9 package org.libreoffice.overlay;
11 import android.content.Context;
12 import android.graphics.Canvas;
13 import android.graphics.Color;
14 import android.graphics.Paint;
15 import android.graphics.PointF;
16 import android.graphics.RectF;
17 import android.util.AttributeSet;
18 import android.view.MotionEvent;
19 import android.view.View;
21 import org.libreoffice.LibreOfficeMainActivity;
22 import org.libreoffice.R;
23 import org.libreoffice.canvas.AdjustLengthLine;
24 import org.libreoffice.canvas.CalcSelectionBox;
25 import org.libreoffice.canvas.Cursor;
26 import org.libreoffice.canvas.GraphicSelection;
27 import org.libreoffice.canvas.PageNumberRect;
28 import org.libreoffice.canvas.SelectionHandle;
29 import org.libreoffice.canvas.SelectionHandleEnd;
30 import org.libreoffice.canvas.SelectionHandleMiddle;
31 import org.libreoffice.canvas.SelectionHandleStart;
32 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
33 import org.mozilla.gecko.gfx.LayerView;
34 import org.mozilla.gecko.gfx.RectUtils;
36 import java.util.ArrayList;
37 import java.util.List;
39 /**
40 * Document overlay view is responsible for showing the client drawn overlay
41 * elements like cursor, selection and graphic selection, and manipulate them.
43 public class DocumentOverlayView extends View implements View.OnTouchListener {
44 private static final String LOGTAG = DocumentOverlayView.class.getSimpleName();
46 private static final int CURSOR_BLINK_TIME = 500;
48 private boolean mInitialized = false;
50 private List<RectF> mSelections = new ArrayList<RectF>();
51 private List<RectF> mScaledSelections = new ArrayList<RectF>();
52 private Paint mSelectionPaint = new Paint();
53 private boolean mSelectionsVisible;
55 private GraphicSelection mGraphicSelection;
57 private boolean mGraphicSelectionMove = false;
59 private LayerView mLayerView;
61 private SelectionHandle mHandleMiddle;
62 private SelectionHandle mHandleStart;
63 private SelectionHandle mHandleEnd;
65 private Cursor mCursor;
67 private SelectionHandle mDragHandle = null;
69 private List<RectF> mPartPageRectangles;
70 private PageNumberRect mPageNumberRect;
71 private boolean mPageNumberAvailable = false;
72 private int previousIndex = 0; // previous page number, used to compare with the current
73 private CalcHeadersController mCalcHeadersController;
75 private CalcSelectionBox mCalcSelectionBox;
76 private boolean mCalcSelectionBoxDragging;
77 private AdjustLengthLine mAdjustLengthLine;
78 private boolean mAdjustLengthLineDragging;
80 public DocumentOverlayView(Context context) {
81 super(context);
84 public DocumentOverlayView(Context context, AttributeSet attrs) {
85 super(context, attrs);
88 public DocumentOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
89 super(context, attrs, defStyleAttr);
92 /**
93 * Initialize the selection and cursor view.
95 public void initialize(LayerView layerView) {
96 if (!mInitialized) {
97 setOnTouchListener(this);
98 mLayerView = layerView;
100 mCursor = new Cursor();
101 mCursor.setVisible(false);
103 mSelectionPaint.setColor(Color.BLUE);
104 mSelectionPaint.setAlpha(50);
105 mSelectionsVisible = false;
107 mGraphicSelection = new GraphicSelection((LibreOfficeMainActivity) getContext());
108 mGraphicSelection.setVisible(false);
110 postDelayed(cursorAnimation, CURSOR_BLINK_TIME);
112 mHandleMiddle = new SelectionHandleMiddle((LibreOfficeMainActivity) getContext());
113 mHandleStart = new SelectionHandleStart((LibreOfficeMainActivity) getContext());
114 mHandleEnd = new SelectionHandleEnd((LibreOfficeMainActivity) getContext());
116 mInitialized = true;
121 * Change the cursor position.
122 * @param position - new position of the cursor
124 public void changeCursorPosition(RectF position) {
125 if (RectUtils.fuzzyEquals(mCursor.mPosition, position)) {
126 return;
128 mCursor.mPosition = position;
130 ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics();
131 repositionWithViewport(metrics.viewportRectLeft, metrics.viewportRectTop, metrics.zoomFactor);
135 * Change the text selection rectangles.
136 * @param selectionRects - list of text selection rectangles
138 public void changeSelections(List<RectF> selectionRects) {
139 mSelections = selectionRects;
141 ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics();
142 repositionWithViewport(metrics.viewportRectLeft, metrics.viewportRectTop, metrics.zoomFactor);
146 * Change the graphic selection rectangle.
147 * @param rectangle - new graphic selection rectangle
149 public void changeGraphicSelection(RectF rectangle) {
150 if (RectUtils.fuzzyEquals(mGraphicSelection.mRectangle, rectangle)) {
151 return;
154 mGraphicSelection.mRectangle = rectangle;
156 ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics();
157 repositionWithViewport(metrics.viewportRectLeft, metrics.viewportRectTop, metrics.zoomFactor);
160 public void repositionWithViewport(float x, float y, float zoom) {
161 RectF rect = convertToScreen(mCursor.mPosition, x, y, zoom);
162 mCursor.reposition(rect);
164 rect = convertToScreen(mHandleMiddle.mDocumentPosition, x, y, zoom);
165 mHandleMiddle.reposition(rect.left, rect.bottom);
167 rect = convertToScreen(mHandleStart.mDocumentPosition, x, y, zoom);
168 mHandleStart.reposition(rect.left, rect.bottom);
170 rect = convertToScreen(mHandleEnd.mDocumentPosition, x, y, zoom);
171 mHandleEnd.reposition(rect.left, rect.bottom);
173 mScaledSelections.clear();
174 for (RectF selection : mSelections) {
175 RectF scaledSelection = convertToScreen(selection, x, y, zoom);
176 mScaledSelections.add(scaledSelection);
179 if (mCalcSelectionBox != null) {
180 rect = convertToScreen(mCalcSelectionBox.mDocumentPosition, x, y, zoom);
181 mCalcSelectionBox.reposition(rect);
184 if (mGraphicSelection != null && mGraphicSelection.mRectangle != null) {
185 RectF scaledGraphicSelection = convertToScreen(mGraphicSelection.mRectangle, x, y, zoom);
186 mGraphicSelection.reposition(scaledGraphicSelection);
189 invalidate();
193 * Convert the input rectangle from document to screen coordinates
194 * according to current viewport data (x, y, zoom).
196 private static RectF convertToScreen(RectF inputRect, float x, float y, float zoom) {
197 RectF rect = RectUtils.scale(inputRect, zoom);
198 rect.offset(-x, -y);
199 return rect;
203 * Set part page rectangles and initialize a page number rectangle object
204 * (canvas element).
206 public void setPartPageRectangles (List<RectF> rectangles) {
207 mPartPageRectangles = rectangles;
208 mPageNumberRect = new PageNumberRect();
209 mPageNumberAvailable = true;
213 * Drawing on canvas.
215 @Override
216 protected void onDraw(Canvas canvas) {
217 super.onDraw(canvas);
219 mCursor.draw(canvas);
221 if (mPageNumberAvailable) {
222 mPageNumberRect.draw(canvas);
225 mHandleMiddle.draw(canvas);
226 mHandleStart.draw(canvas);
227 mHandleEnd.draw(canvas);
229 if (mSelectionsVisible) {
230 for (RectF selection : mScaledSelections) {
231 canvas.drawRect(selection, mSelectionPaint);
235 if (mCalcSelectionBox != null) {
236 mCalcSelectionBox.draw(canvas);
239 mGraphicSelection.draw(canvas);
241 if (mCalcHeadersController != null) {
242 mCalcHeadersController.showHeaders();
245 if (mAdjustLengthLine != null) {
246 mAdjustLengthLine.draw(canvas);
251 * Cursor animation function. Switch the alpha between opaque and fully transparent.
253 private Runnable cursorAnimation = new Runnable() {
254 public void run() {
255 if (mCursor.isVisible()) {
256 mCursor.cycleAlpha();
257 invalidate();
259 postDelayed(cursorAnimation, CURSOR_BLINK_TIME);
264 * Show the cursor on the view.
266 public void showCursor() {
267 if (!mCursor.isVisible()) {
268 mCursor.setVisible(true);
269 invalidate();
274 * Hide the cursor.
276 public void hideCursor() {
277 if (mCursor.isVisible()) {
278 mCursor.setVisible(false);
279 invalidate();
284 * Calculate and show page number according to current viewport position.
285 * In particular, this function compares the middle point of the
286 * view port with page rectangles and finds out which page the user
287 * is currently on. It does not update the associated canvas element
288 * unless there is a change of page number.
290 public void showPageNumberRect() {
291 if (null == mPartPageRectangles) return;
292 PointF midPoint = mLayerView.getLayerClient().convertViewPointToLayerPoint(new PointF(getWidth()/2f, getHeight()/2f));
293 int index = previousIndex;
294 // search which page the user in currently on. can enhance the search algorithm to binary search if necessary
295 for (RectF page : mPartPageRectangles) {
296 if (page.top < midPoint.y && midPoint.y < page.bottom) {
297 index = mPartPageRectangles.indexOf(page) + 1;
298 break;
301 // index == 0 applies to non-text document, i.e. don't show page info on non-text docs
302 if (index == 0) {
303 return;
305 // if page rectangle canvas element is not visible or the page number is changed, show
306 if (!mPageNumberRect.isVisible() || index != previousIndex) {
307 previousIndex = index;
308 String pageNumberString = getContext().getString(R.string.page) + " " + index + "/" + mPartPageRectangles.size();
309 mPageNumberRect.setPageNumberString(pageNumberString);
310 mPageNumberRect.setVisible(true);
311 invalidate();
316 * Hide page number rectangle canvas element.
318 public void hidePageNumberRect() {
319 if (null == mPageNumberRect) return;
320 if (mPageNumberRect.isVisible()) {
321 mPageNumberRect.setVisible(false);
322 invalidate();
327 * Show text selection rectangles.
329 public void showSelections() {
330 if (!mSelectionsVisible) {
331 mSelectionsVisible = true;
332 invalidate();
337 * Hide text selection rectangles.
339 public void hideSelections() {
340 if (mSelectionsVisible) {
341 mSelectionsVisible = false;
342 invalidate();
347 * Show the graphic selection on the view.
349 public void showGraphicSelection() {
350 if (!mGraphicSelection.isVisible()) {
351 mGraphicSelectionMove = false;
352 mGraphicSelection.reset();
353 mGraphicSelection.setVisible(true);
354 invalidate();
359 * Hide the graphic selection.
361 public void hideGraphicSelection() {
362 if (mGraphicSelection.isVisible()) {
363 mGraphicSelection.setVisible(false);
364 invalidate();
369 * Handle the triggered touch event.
371 @Override
372 public boolean onTouch(View view, MotionEvent event) {
373 PointF point = new PointF(event.getX(), event.getY());
374 switch (event.getActionMasked()) {
375 case MotionEvent.ACTION_DOWN: {
376 if (mAdjustLengthLine != null && !mAdjustLengthLine.contains(point.x, point.y)) {
377 mAdjustLengthLine.setVisible(false);
378 invalidate();
380 if (mGraphicSelection.isVisible()) {
381 // Check if inside graphic selection was hit
382 if (mGraphicSelection.contains(point.x, point.y)) {
383 mGraphicSelectionMove = true;
384 mGraphicSelection.dragStart(point);
385 invalidate();
386 return true;
388 } else {
389 if (mHandleStart.contains(point.x, point.y)) {
390 mHandleStart.dragStart(point);
391 mDragHandle = mHandleStart;
392 return true;
393 } else if (mHandleEnd.contains(point.x, point.y)) {
394 mHandleEnd.dragStart(point);
395 mDragHandle = mHandleEnd;
396 return true;
397 } else if (mHandleMiddle.contains(point.x, point.y)) {
398 mHandleMiddle.dragStart(point);
399 mDragHandle = mHandleMiddle;
400 return true;
401 } else if (mCalcSelectionBox != null &&
402 mCalcSelectionBox.contains(point.x, point.y) &&
403 !mHandleStart.isVisible()) {
404 mCalcSelectionBox.dragStart(point);
405 mCalcSelectionBoxDragging = true;
406 return true;
407 } else if (mAdjustLengthLine != null &&
408 mAdjustLengthLine.contains(point.x, point.y)) {
409 mAdjustLengthLine.dragStart(point);
410 mAdjustLengthLineDragging = true;
411 return true;
415 case MotionEvent.ACTION_UP: {
416 if (mGraphicSelection.isVisible() && mGraphicSelectionMove) {
417 mGraphicSelection.dragEnd(point);
418 mGraphicSelectionMove = false;
419 invalidate();
420 return true;
421 } else if (mDragHandle != null) {
422 mDragHandle.dragEnd(point);
423 mDragHandle = null;
424 } else if (mCalcSelectionBoxDragging) {
425 mCalcSelectionBox.dragEnd(point);
426 mCalcSelectionBoxDragging = false;
427 } else if (mAdjustLengthLineDragging) {
428 mAdjustLengthLine.dragEnd(point);
429 mAdjustLengthLineDragging = false;
430 invalidate();
433 case MotionEvent.ACTION_MOVE: {
434 if (mGraphicSelection.isVisible() && mGraphicSelectionMove) {
435 mGraphicSelection.dragging(point);
436 invalidate();
437 return true;
438 } else if (mDragHandle != null) {
439 mDragHandle.dragging(point);
440 } else if (mCalcSelectionBoxDragging) {
441 mCalcSelectionBox.dragging(point);
442 } else if (mAdjustLengthLineDragging) {
443 mAdjustLengthLine.dragging(point);
444 invalidate();
448 return false;
452 * Change the handle document position.
453 * @param type - the type of the handle
454 * @param position - the new document position
456 public void positionHandle(SelectionHandle.HandleType type, RectF position) {
457 SelectionHandle handle = getHandleForType(type);
458 if (RectUtils.fuzzyEquals(handle.mDocumentPosition, position)) {
459 return;
462 RectUtils.assign(handle.mDocumentPosition, position);
464 ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics();
465 repositionWithViewport(metrics.viewportRectLeft, metrics.viewportRectTop, metrics.zoomFactor);
469 * Hide the handle.
470 * @param type - type of the handle
472 public void hideHandle(SelectionHandle.HandleType type) {
473 SelectionHandle handle = getHandleForType(type);
474 if (handle.isVisible()) {
475 handle.setVisible(false);
476 invalidate();
481 * Show the handle.
482 * @param type - type of the handle
484 public void showHandle(SelectionHandle.HandleType type) {
485 SelectionHandle handle = getHandleForType(type);
486 if (!handle.isVisible()) {
487 handle.setVisible(true);
488 invalidate();
493 * Returns the handle instance for the input type.
495 private SelectionHandle getHandleForType(SelectionHandle.HandleType type) {
496 switch(type) {
497 case START:
498 return mHandleStart;
499 case END:
500 return mHandleEnd;
501 case MIDDLE:
502 return mHandleMiddle;
504 return null;
507 public RectF getCurrentCursorPosition() {
508 return mCursor.mPosition;
511 public void setCalcHeadersController(CalcHeadersController calcHeadersController) {
512 mCalcHeadersController = calcHeadersController;
513 mCalcSelectionBox = new CalcSelectionBox((LibreOfficeMainActivity) getContext());
516 public void showCellSelection(RectF cellCursorRect) {
517 if (mCalcHeadersController == null || mCalcSelectionBox == null) return;
518 if (RectUtils.fuzzyEquals(mCalcSelectionBox.mDocumentPosition, cellCursorRect)) {
519 return;
522 // show selection on main GL view (i.e. in the document)
523 RectUtils.assign(mCalcSelectionBox.mDocumentPosition, cellCursorRect);
524 mCalcSelectionBox.setVisible(true);
526 ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics();
527 repositionWithViewport(metrics.viewportRectLeft, metrics.viewportRectTop, metrics.zoomFactor);
529 // show selection on headers
530 if (!mCalcHeadersController.pendingRowOrColumnSelectionToShowUp()) {
531 showHeaderSelection(cellCursorRect);
532 } else {
533 mCalcHeadersController.setPendingRowOrColumnSelectionToShowUp(false);
537 public void showHeaderSelection(RectF rect) {
538 if (mCalcHeadersController == null) return;
539 mCalcHeadersController.showHeaderSelection(rect);
542 public void showAdjustLengthLine(boolean isRow, final CalcHeadersView view) {
543 mAdjustLengthLine = new AdjustLengthLine((LibreOfficeMainActivity) getContext(), view, isRow, getWidth(), getHeight());
544 ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics();
545 RectF position = convertToScreen(mCalcSelectionBox.mDocumentPosition, metrics.viewportRectLeft, metrics.viewportRectTop, metrics.zoomFactor);
546 mAdjustLengthLine.setScreenRect(position);
547 mAdjustLengthLine.setVisible(true);
548 invalidate();
552 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */