1 package org
.libreoffice
;
3 import android
.graphics
.Bitmap
;
4 import android
.graphics
.PointF
;
5 import android
.graphics
.RectF
;
6 import android
.util
.Log
;
7 import android
.view
.KeyEvent
;
9 import org
.libreoffice
.canvas
.SelectionHandle
;
10 import org
.mozilla
.gecko
.ZoomConstraints
;
11 import org
.mozilla
.gecko
.gfx
.CairoImage
;
12 import org
.mozilla
.gecko
.gfx
.ComposedTileLayer
;
13 import org
.mozilla
.gecko
.gfx
.GeckoLayerClient
;
14 import org
.mozilla
.gecko
.gfx
.ImmutableViewportMetrics
;
15 import org
.mozilla
.gecko
.gfx
.SubTile
;
17 import java
.util
.ArrayList
;
18 import java
.util
.List
;
19 import java
.util
.concurrent
.LinkedBlockingQueue
;
22 * Thread that communicates with LibreOffice through LibreOfficeKit JNI interface. The thread
23 * consumes events from other threads (mainly the UI thread) and acts accordingly.
25 class LOKitThread
extends Thread
{
26 private static final String LOGTAG
= LOKitThread
.class.getSimpleName();
28 private final LinkedBlockingQueue
<LOEvent
> mEventQueue
= new LinkedBlockingQueue
<LOEvent
>();
30 private TileProvider mTileProvider
;
31 private InvalidationHandler mInvalidationHandler
;
32 private ImmutableViewportMetrics mViewportMetrics
;
33 private GeckoLayerClient mLayerClient
;
34 private final LibreOfficeMainActivity mContext
;
36 LOKitThread(LibreOfficeMainActivity context
) {
38 mInvalidationHandler
= null;
39 TileProviderFactory
.initialize();
43 * Starting point of the thread. Processes events that gather in the queue.
50 event
= mEventQueue
.take();
52 } catch (InterruptedException exception
) {
53 throw new RuntimeException(exception
);
59 * Viewport changed, Recheck if tiles need to be added / removed.
61 private void tileReevaluationRequest(ComposedTileLayer composedTileLayer
) {
62 if (mTileProvider
== null) {
65 List
<SubTile
> tiles
= new ArrayList
<SubTile
>();
67 mLayerClient
.beginDrawing();
68 composedTileLayer
.addNewTiles(tiles
);
69 mLayerClient
.endDrawing();
71 for (SubTile tile
: tiles
) {
72 TileIdentifier tileId
= tile
.id
;
73 CairoImage image
= mTileProvider
.createTile(tileId
.x
, tileId
.y
, tileId
.size
, tileId
.zoom
);
74 mLayerClient
.beginDrawing();
78 mLayerClient
.endDrawing();
79 mLayerClient
.forceRender();
82 mLayerClient
.beginDrawing();
83 composedTileLayer
.markTiles();
84 composedTileLayer
.clearMarkedTiles();
85 mLayerClient
.endDrawing();
86 mLayerClient
.forceRender();
90 * Invalidate tiles that intersect the input rect.
92 private void tileInvalidation(RectF rect
) {
93 if (mLayerClient
== null || mTileProvider
== null) {
97 mLayerClient
.beginDrawing();
99 List
<SubTile
> tiles
= new ArrayList
<SubTile
>();
100 mLayerClient
.invalidateTiles(tiles
, rect
);
102 for (SubTile tile
: tiles
) {
103 CairoImage image
= mTileProvider
.createTile(tile
.id
.x
, tile
.id
.y
, tile
.id
.size
, tile
.id
.zoom
);
104 tile
.setImage(image
);
107 mLayerClient
.endDrawing();
108 mLayerClient
.forceRender();
112 * Handle the geometry change + draw.
114 private void redraw(boolean resetZoomAndPosition
) {
115 if (mLayerClient
== null || mTileProvider
== null) {
116 // called too early...
120 mLayerClient
.setPageRect(0, 0, mTileProvider
.getPageWidth(), mTileProvider
.getPageHeight());
121 mViewportMetrics
= mLayerClient
.getViewportMetrics();
122 mLayerClient
.setViewportMetrics(mViewportMetrics
);
124 if (resetZoomAndPosition
) {
125 zoomAndRepositionTheDocument();
128 mLayerClient
.forceRedraw();
129 mLayerClient
.forceRender();
133 * Reposition the view (zoom and position) when the document is firstly shown. This is document type dependent.
135 private void zoomAndRepositionTheDocument() {
136 if (mTileProvider
.isSpreadsheet()) {
137 // Don't do anything for spreadsheets - show at 100%
138 } else if (mTileProvider
.isTextDocument()) {
139 // Always zoom text document to the beginning of the document and centered by width
140 float centerY
= mViewportMetrics
.getCssViewport().centerY();
141 mLayerClient
.zoomTo(new RectF(0, centerY
, mTileProvider
.getPageWidth(), centerY
));
143 // Other documents - always show the whole document on the screen,
144 // regardless of document shape and orientation.
145 if (mViewportMetrics
.getViewport().width() < mViewportMetrics
.getViewport().height()) {
146 mLayerClient
.zoomTo(mTileProvider
.getPageWidth(), 0);
148 mLayerClient
.zoomTo(0, mTileProvider
.getPageHeight());
154 * Invalidate everything + handle the geometry change
156 private void refresh(boolean resetZoomAndPosition
) {
157 mLayerClient
.clearAndResetlayers();
158 redraw(resetZoomAndPosition
);
159 updatePartPageRectangles();
160 if (mTileProvider
!= null && mTileProvider
.isSpreadsheet()) {
166 * Update part page rectangles which hold positions of each document page.
167 * Result is stored in DocumentOverlayView class.
169 private void updatePartPageRectangles() {
170 if (mTileProvider
== null) {
171 Log
.d(LOGTAG
, "mTileProvider==null when calling updatePartPageRectangles");
174 String partPageRectString
= ((LOKitTileProvider
) mTileProvider
).getPartPageRectangles();
175 List
<RectF
> partPageRectangles
= mInvalidationHandler
.convertPayloadToRectangles(partPageRectString
);
176 mContext
.getDocumentOverlay().setPartPageRectangles(partPageRectangles
);
179 private void updatePageSize(int pageWidth
, int pageHeight
){
180 mTileProvider
.setDocumentSize(pageWidth
, pageHeight
);
184 private void updateZoomConstraints() {
185 if (mTileProvider
== null) return;
186 mLayerClient
= mContext
.getLayerClient();
187 // Set default zoom to the page width and min zoom so that the whole page is visible
188 final float pageHeightZoom
= mLayerClient
.getViewportMetrics().getHeight() / mTileProvider
.getPageHeight();
189 final float pageWidthZoom
= mLayerClient
.getViewportMetrics().getWidth() / mTileProvider
.getPageWidth();
190 final float minZoom
= Math
.min(pageWidthZoom
, pageHeightZoom
);
191 mLayerClient
.setZoomConstraints(new ZoomConstraints(pageWidthZoom
, minZoom
, 0f
));
195 * Change part of the document.
197 private void changePart(int partIndex
) {
198 LOKitShell
.showProgressSpinner(mContext
);
199 mTileProvider
.changePart(partIndex
);
200 mViewportMetrics
= mLayerClient
.getViewportMetrics();
201 // mLayerClient.setViewportMetrics(mViewportMetrics.scaleTo(0.9f, new PointF()));
203 LOKitShell
.hideProgressSpinner(mContext
);
207 * Handle load document event.
208 * @param filePath - filePath to where the document is located
209 * @return Whether the document has been loaded successfully.
211 private boolean loadDocument(String filePath
) {
212 mLayerClient
= mContext
.getLayerClient();
214 mInvalidationHandler
= new InvalidationHandler(mContext
);
215 mTileProvider
= TileProviderFactory
.create(mContext
, mInvalidationHandler
, filePath
);
217 if (mTileProvider
.isReady()) {
218 LOKitShell
.showProgressSpinner(mContext
);
219 updateZoomConstraints();
221 LOKitShell
.hideProgressSpinner(mContext
);
230 * Handle load new document event.
231 * @param filePath - filePath to where new document is to be created
232 * @param fileType - fileType what type of new document is to be loaded
234 private void loadNewDocument(String filePath
, String fileType
) {
235 boolean ok
= loadDocument(fileType
);
237 mTileProvider
.saveDocumentAs(filePath
, true);
242 * Save the currently loaded document.
244 private void saveDocumentAs(String filePath
, String fileType
, boolean bTakeOwnership
) {
245 if (mTileProvider
== null) {
246 Log
.e(LOGTAG
, "Error in saving, Tile Provider instance is null");
248 mTileProvider
.saveDocumentAs(filePath
, fileType
, bTakeOwnership
);
253 * Close the currently loaded document.
255 private void closeDocument() {
256 if (mTileProvider
!= null) {
257 mTileProvider
.close();
258 mTileProvider
= null;
263 * Process the input event.
265 private void processEvent(LOEvent event
) {
266 switch (event
.mType
) {
268 loadDocument(event
.filePath
);
270 case LOEvent
.LOAD_NEW
:
271 loadNewDocument(event
.filePath
, event
.fileType
);
273 case LOEvent
.SAVE_AS
:
274 saveDocumentAs(event
.filePath
, event
.fileType
, true);
276 case LOEvent
.SAVE_COPY_AS
:
277 saveDocumentAs(event
.filePath
, event
.fileType
, false);
282 case LOEvent
.SIZE_CHANGED
:
285 case LOEvent
.CHANGE_PART
:
286 changePart(event
.mPartIndex
);
288 case LOEvent
.TILE_INVALIDATION
:
289 tileInvalidation(event
.mInvalidationRect
);
291 case LOEvent
.THUMBNAIL
:
292 createThumbnail(event
.mTask
);
295 touch(event
.mTouchType
, event
.mDocumentCoordinate
);
297 case LOEvent
.KEY_EVENT
:
298 keyEvent(event
.mKeyEvent
);
300 case LOEvent
.TILE_REEVALUATION_REQUEST
:
301 tileReevaluationRequest(event
.mComposedTileLayer
);
303 case LOEvent
.CHANGE_HANDLE_POSITION
:
304 changeHandlePosition(event
.mHandleType
, event
.mDocumentCoordinate
);
306 case LOEvent
.SWIPE_LEFT
:
307 if (null != mTileProvider
) onSwipeLeft();
309 case LOEvent
.SWIPE_RIGHT
:
310 if (null != mTileProvider
) onSwipeRight();
312 case LOEvent
.NAVIGATION_CLICK
:
313 mInvalidationHandler
.changeStateTo(InvalidationHandler
.OverlayState
.NONE
);
315 case LOEvent
.UNO_COMMAND
:
316 if (null == mTileProvider
)
317 Log
.e(LOGTAG
, "no mTileProvider when trying to process "+event
.mValue
+" from UNO_COMMAND "+event
.mString
);
319 mTileProvider
.postUnoCommand(event
.mString
, event
.mValue
);
321 case LOEvent
.UPDATE_PART_PAGE_RECT
:
322 updatePartPageRectangles();
324 case LOEvent
.UPDATE_ZOOM_CONSTRAINTS
:
325 updateZoomConstraints();
327 case LOEvent
.UPDATE_CALC_HEADERS
:
330 case LOEvent
.UNO_COMMAND_NOTIFY
:
331 if (null == mTileProvider
)
332 Log
.e(LOGTAG
, "no mTileProvider when trying to process "+event
.mValue
+" from UNO_COMMAND "+event
.mString
);
334 mTileProvider
.postUnoCommand(event
.mString
, event
.mValue
, event
.mNotify
);
336 case LOEvent
.REFRESH
:
339 case LOEvent
.PAGE_SIZE_CHANGED
:
340 updatePageSize(event
.mPageWidth
, event
.mPageHeight
);
345 private void updateCalcHeaders() {
346 if (null == mTileProvider
) return;
347 LOKitTileProvider tileProvider
= (LOKitTileProvider
)mTileProvider
;
348 String values
= tileProvider
.getCalcHeaders();
349 mContext
.getCalcHeadersController().setHeaders(values
);
353 * Request a change of the handle position.
355 private void changeHandlePosition(SelectionHandle
.HandleType handleType
, PointF documentCoordinate
) {
356 switch (handleType
) {
358 mTileProvider
.setTextSelectionReset(documentCoordinate
);
361 mTileProvider
.setTextSelectionStart(documentCoordinate
);
364 mTileProvider
.setTextSelectionEnd(documentCoordinate
);
370 * Processes key events.
372 private void keyEvent(KeyEvent keyEvent
) {
373 if (!LOKitShell
.isEditingEnabled()) {
376 if (mTileProvider
== null) {
379 mInvalidationHandler
.keyEvent();
380 mTileProvider
.sendKeyEvent(keyEvent
);
384 * Process swipe left event.
386 private void onSwipeLeft() {
387 mTileProvider
.onSwipeLeft();
391 * Process swipe right event.
393 private void onSwipeRight() {
394 mTileProvider
.onSwipeRight();
398 * Processes touch events.
400 private void touch(String touchType
, PointF documentCoordinate
) {
401 if (mTileProvider
== null || mViewportMetrics
== null) {
405 // to handle hyperlinks, enable single tap even in the Viewer
406 boolean editing
= LOKitShell
.isEditingEnabled();
407 float zoomFactor
= mViewportMetrics
.getZoomFactor();
409 if (touchType
.equals("LongPress")) {
410 mInvalidationHandler
.changeStateTo(InvalidationHandler
.OverlayState
.TRANSITION
);
411 mTileProvider
.mouseButtonDown(documentCoordinate
, 1, zoomFactor
);
412 mTileProvider
.mouseButtonUp(documentCoordinate
, 1, zoomFactor
);
413 mTileProvider
.mouseButtonDown(documentCoordinate
, 2, zoomFactor
);
414 mTileProvider
.mouseButtonUp(documentCoordinate
, 2, zoomFactor
);
415 } else if (touchType
.equals("SingleTap")) {
416 mInvalidationHandler
.changeStateTo(InvalidationHandler
.OverlayState
.TRANSITION
);
417 mTileProvider
.mouseButtonDown(documentCoordinate
, 1, zoomFactor
);
418 mTileProvider
.mouseButtonUp(documentCoordinate
, 1, zoomFactor
);
419 } else if (touchType
.equals("GraphicSelectionStart") && editing
) {
420 mTileProvider
.setGraphicSelectionStart(documentCoordinate
);
421 } else if (touchType
.equals("GraphicSelectionEnd") && editing
) {
422 mTileProvider
.setGraphicSelectionEnd(documentCoordinate
);
427 * Create thumbnail for the requested document task.
429 private void createThumbnail(final ThumbnailCreator
.ThumbnailCreationTask task
) {
430 final Bitmap bitmap
= task
.getThumbnail(mTileProvider
);
431 task
.applyBitmap(bitmap
);
437 public void queueEvent(LOEvent event
) {
438 mEventQueue
.add(event
);
442 * Clear all events in the queue (used when document is closed).
444 public void clearQueue() {
449 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */