1 /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
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
;
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
) {
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
);
93 * Initialize the selection and cursor view.
95 public void initialize(LayerView layerView
) {
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());
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
)) {
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
)) {
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
);
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
);
203 * Set part page rectangles and initialize a page number rectangle object
206 public void setPartPageRectangles (List
<RectF
> rectangles
) {
207 mPartPageRectangles
= rectangles
;
208 mPageNumberRect
= new PageNumberRect();
209 mPageNumberAvailable
= true;
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() {
255 if (mCursor
.isVisible()) {
256 mCursor
.cycleAlpha();
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);
276 public void hideCursor() {
277 if (mCursor
.isVisible()) {
278 mCursor
.setVisible(false);
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;
301 // index == 0 applies to non-text document, i.e. don't show page info on non-text docs
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);
316 * Hide page number rectangle canvas element.
318 public void hidePageNumberRect() {
319 if (null == mPageNumberRect
) return;
320 if (mPageNumberRect
.isVisible()) {
321 mPageNumberRect
.setVisible(false);
327 * Show text selection rectangles.
329 public void showSelections() {
330 if (!mSelectionsVisible
) {
331 mSelectionsVisible
= true;
337 * Hide text selection rectangles.
339 public void hideSelections() {
340 if (mSelectionsVisible
) {
341 mSelectionsVisible
= false;
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);
359 * Hide the graphic selection.
361 public void hideGraphicSelection() {
362 if (mGraphicSelection
.isVisible()) {
363 mGraphicSelection
.setVisible(false);
369 * Handle the triggered touch event.
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);
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
);
389 if (mHandleStart
.contains(point
.x
, point
.y
)) {
390 mHandleStart
.dragStart(point
);
391 mDragHandle
= mHandleStart
;
393 } else if (mHandleEnd
.contains(point
.x
, point
.y
)) {
394 mHandleEnd
.dragStart(point
);
395 mDragHandle
= mHandleEnd
;
397 } else if (mHandleMiddle
.contains(point
.x
, point
.y
)) {
398 mHandleMiddle
.dragStart(point
);
399 mDragHandle
= mHandleMiddle
;
401 } else if (mCalcSelectionBox
!= null &&
402 mCalcSelectionBox
.contains(point
.x
, point
.y
) &&
403 !mHandleStart
.isVisible()) {
404 mCalcSelectionBox
.dragStart(point
);
405 mCalcSelectionBoxDragging
= true;
407 } else if (mAdjustLengthLine
!= null &&
408 mAdjustLengthLine
.contains(point
.x
, point
.y
)) {
409 mAdjustLengthLine
.dragStart(point
);
410 mAdjustLengthLineDragging
= true;
415 case MotionEvent
.ACTION_UP
: {
416 if (mGraphicSelection
.isVisible() && mGraphicSelectionMove
) {
417 mGraphicSelection
.dragEnd(point
);
418 mGraphicSelectionMove
= false;
421 } else if (mDragHandle
!= null) {
422 mDragHandle
.dragEnd(point
);
424 } else if (mCalcSelectionBoxDragging
) {
425 mCalcSelectionBox
.dragEnd(point
);
426 mCalcSelectionBoxDragging
= false;
427 } else if (mAdjustLengthLineDragging
) {
428 mAdjustLengthLine
.dragEnd(point
);
429 mAdjustLengthLineDragging
= false;
433 case MotionEvent
.ACTION_MOVE
: {
434 if (mGraphicSelection
.isVisible() && mGraphicSelectionMove
) {
435 mGraphicSelection
.dragging(point
);
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
);
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
)) {
462 RectUtils
.assign(handle
.mDocumentPosition
, position
);
464 ImmutableViewportMetrics metrics
= mLayerView
.getViewportMetrics();
465 repositionWithViewport(metrics
.viewportRectLeft
, metrics
.viewportRectTop
, metrics
.zoomFactor
);
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);
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);
493 * Returns the handle instance for the input type.
495 private SelectionHandle
getHandleForType(SelectionHandle
.HandleType type
) {
502 return mHandleMiddle
;
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
)) {
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
);
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);
552 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */