/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.libreoffice; import android.content.Context; import android.graphics.Bitmap; import android.graphics.PointF; import android.os.Build; import android.print.PrintAttributes; import android.print.PrintDocumentAdapter; import android.print.PrintManager; import android.util.Log; import android.view.KeyEvent; import android.widget.Toast; import org.json.JSONException; import org.json.JSONObject; import org.libreoffice.kit.DirectBufferAllocator; import org.libreoffice.kit.Document; import org.libreoffice.kit.LibreOfficeKit; import org.libreoffice.kit.Office; import org.mozilla.gecko.gfx.BufferedCairoImage; import org.mozilla.gecko.gfx.CairoImage; import org.mozilla.gecko.gfx.IntSize; import java.io.File; import java.nio.ByteBuffer; /** * LOKit implementation of TileProvider. */ class LOKitTileProvider implements TileProvider { private static final String LOGTAG = LOKitTileProvider.class.getSimpleName(); private static final int TILE_SIZE = 256; private final float mTileWidth; private final float mTileHeight; private String mInputFile; private Office mOffice; private Document mDocument; private final boolean mIsReady; private final LibreOfficeMainActivity mContext; private final float mDPI; private float mWidthTwip; private float mHeightTwip; private final Document.MessageCallback mMessageCallback; private final long objectCreationTime = System.currentTimeMillis(); /** * Initialize LOKit and load the document. * @param messageCallback - callback for messages retrieved from LOKit * @param input - input path of the document */ LOKitTileProvider(LibreOfficeMainActivity context, InvalidationHandler messageCallback, String input) { mContext = context; mMessageCallback = messageCallback; LibreOfficeKit.putenv("SAL_LOG=+WARN+INFO"); LibreOfficeKit.init(mContext); mOffice = new Office(LibreOfficeKit.getLibreOfficeKitHandle()); mOffice.setMessageCallback(messageCallback); mOffice.setOptionalFeatures(Document.LOK_FEATURE_DOCUMENT_PASSWORD); mContext.setTileProvider(this); mInputFile = input; Log.i(LOGTAG, "====> Loading file '" + input + "'"); File fileToBeEncoded = new File(input); String encodedFileName = android.net.Uri.encode(fileToBeEncoded.getName()); mDocument = mOffice.documentLoad( (new File(fileToBeEncoded.getParent(),encodedFileName)).getPath() ); if (mDocument == null && !mContext.isPasswordProtected()) { Log.i(LOGTAG, "====> mOffice.documentLoad() returned null, trying to restart 'Office' and loading again"); mOffice.destroy(); Log.i(LOGTAG, "====> mOffice.destroy() done"); ByteBuffer handle = LibreOfficeKit.getLibreOfficeKitHandle(); Log.i(LOGTAG, "====> getLibreOfficeKitHandle() = " + handle); mOffice = new Office(handle); Log.i(LOGTAG, "====> new Office created"); mOffice.setMessageCallback(messageCallback); mOffice.setOptionalFeatures(Document.LOK_FEATURE_DOCUMENT_PASSWORD); Log.i(LOGTAG, "====> setup Lokit callback and optional features (password support)"); mDocument = mOffice.documentLoad( (new File(fileToBeEncoded.getParent(),encodedFileName)).getPath() ); } Log.i(LOGTAG, "====> mDocument = " + mDocument); mDPI = LOKitShell.getDpi(mContext); mTileWidth = pixelToTwip(TILE_SIZE, mDPI); mTileHeight = pixelToTwip(TILE_SIZE, mDPI); if (mDocument != null) mDocument.initializeForRendering(); if (checkDocument()) { postLoad(); mIsReady = true; } else { mIsReady = false; } } /** * Triggered after the document is loaded. */ private void postLoad() { mDocument.setMessageCallback(mMessageCallback); resetParts(); // Writer documents always have one part, so hide the navigation drawer. if (mDocument.getDocumentType() == Document.DOCTYPE_TEXT) { mContext.disableNavigationDrawer(); mContext.getToolbarController().hideItem(R.id.action_parts); } // Enable headers for Calc documents if (mDocument.getDocumentType() == Document.DOCTYPE_SPREADSHEET) { mContext.initializeCalcHeaders(); } mDocument.setPart(0); setupDocumentFonts(); LOKitShell.getMainHandler().post(new Runnable() { @Override public void run() { mContext.getDocumentPartViewListAdapter().notifyDataSetChanged(); } }); } public void addPart(){ int parts = mDocument.getParts(); if(mDocument.getDocumentType() == Document.DOCTYPE_SPREADSHEET){ try{ JSONObject jsonObject = new JSONObject(); JSONObject values = new JSONObject(); JSONObject values2 = new JSONObject(); values.put("type", "long"); values.put("value", 0); //add to the last values2.put("type", "string"); values2.put("value", ""); jsonObject.put("Name", values2); jsonObject.put("Index", values); LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Insert", jsonObject.toString())); }catch (JSONException e) { e.printStackTrace(); } } else if (mDocument.getDocumentType() == Document.DOCTYPE_PRESENTATION){ LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:InsertPage")); } String partName = mDocument.getPartName(parts); if (partName.isEmpty()) { partName = getGenericPartName(parts); } mDocument.setPart(parts); resetDocumentSize(); final DocumentPartView partView = new DocumentPartView(parts, partName); mContext.getDocumentPartView().add(partView); } public void resetParts(){ mContext.getDocumentPartView().clear(); if (mDocument.getDocumentType() != Document.DOCTYPE_TEXT) { int parts = mDocument.getParts(); for (int i = 0; i < parts; i++) { String partName = mDocument.getPartName(i); if (partName.isEmpty()) { partName = getGenericPartName(i); } Log.i(LOGTAG, "resetParts: " + partName); mDocument.setPart(i); resetDocumentSize(); final DocumentPartView partView = new DocumentPartView(i, partName); mContext.getDocumentPartView().add(partView); } } } public void renamePart(String partName) { try{ for(int i=0; i> @" + start + " (" + tileSize.width + " " + tileSize.height + " " + (int) twipX + " " + (int) twipY + " " + (int) twipWidth + " " + (int) twipHeight + ")"); mDocument.paintTile(image.getBuffer(), tileSize.width, tileSize.height, (int) twipX, (int) twipY, (int) twipWidth, (int) twipHeight); long stop = System.currentTimeMillis() - objectCreationTime; //Log.i(LOGTAG, "paintTile << @" + stop + " elapsed: " + (stop - start)); } else { if (mDocument == null) { Log.e(LOGTAG, "Document is null!!"); } } } /** * @see TileProvider#thumbnail(int) */ @Override public Bitmap thumbnail(int size) { int widthPixel = getPageWidth(); int heightPixel = getPageHeight(); if (widthPixel > heightPixel) { double ratio = heightPixel / (double) widthPixel; widthPixel = size; heightPixel = (int) (widthPixel * ratio); } else { double ratio = widthPixel / (double) heightPixel; heightPixel = size; widthPixel = (int) (heightPixel * ratio); } Log.w(LOGTAG, "Thumbnail size: " + getPageWidth() + " " + getPageHeight() + " " + widthPixel + " " + heightPixel); ByteBuffer buffer = ByteBuffer.allocateDirect(widthPixel * heightPixel * 4); if (mDocument != null) mDocument.paintTile(buffer, widthPixel, heightPixel, 0, 0, (int) mWidthTwip, (int) mHeightTwip); Bitmap bitmap = null; try { bitmap = Bitmap.createBitmap(widthPixel, heightPixel, Bitmap.Config.ARGB_8888); bitmap.copyPixelsFromBuffer(buffer); } catch (IllegalArgumentException e) { Log.e(LOGTAG, "width (" + widthPixel + ") and height (" + heightPixel + ") must not be 0! (ToDo: likely timing issue)"); } if (bitmap == null) { Log.w(LOGTAG, "Thumbnail not created!"); } return bitmap; } /** * @see TileProvider#close() */ @Override public void close() { Log.i(LOGTAG, "Document destroyed: " + mInputFile); if (mDocument != null) { mDocument.destroy(); mDocument = null; } } /** * @see TileProvider#isDrawing() */ @Override public boolean isDrawing() { return mDocument != null && mDocument.getDocumentType() == Document.DOCTYPE_DRAWING; } /** * @see TileProvider#isTextDocument() */ @Override public boolean isTextDocument() { return mDocument != null && mDocument.getDocumentType() == Document.DOCTYPE_TEXT; } /** * @see TileProvider#isSpreadsheet() */ @Override public boolean isSpreadsheet() { return mDocument != null && mDocument.getDocumentType() == Document.DOCTYPE_SPREADSHEET; } /** * @see TileProvider#isPresentation() */ @Override public boolean isPresentation(){ return mDocument != null && mDocument.getDocumentType() == Document.DOCTYPE_PRESENTATION; } /** * Returns the Unicode character generated by this event or 0. */ private int getCharCode(KeyEvent keyEvent) { switch (keyEvent.getKeyCode()) { case KeyEvent.KEYCODE_DEL: case KeyEvent.KEYCODE_ENTER: return 0; } return keyEvent.getUnicodeChar(); } /** * Returns the integer code representing the key of the event (non-zero for * control keys). */ private int getKeyCode(KeyEvent keyEvent) { switch (keyEvent.getKeyCode()) { case KeyEvent.KEYCODE_DEL: return com.sun.star.awt.Key.BACKSPACE; case KeyEvent.KEYCODE_ENTER: return com.sun.star.awt.Key.RETURN; } return 0; } /** * @see TileProvider#sendKeyEvent(android.view.KeyEvent) */ @Override public void sendKeyEvent(KeyEvent keyEvent) { switch (keyEvent.getAction()) { case KeyEvent.ACTION_MULTIPLE: String keyString = keyEvent.getCharacters(); for (int i = 0; i < keyString.length(); i++) { int codePoint = keyString.codePointAt(i); mDocument.postKeyEvent(Document.KEY_EVENT_PRESS, codePoint, getKeyCode(keyEvent)); } break; case KeyEvent.ACTION_DOWN: mDocument.postKeyEvent(Document.KEY_EVENT_PRESS, getCharCode(keyEvent), getKeyCode(keyEvent)); break; case KeyEvent.ACTION_UP: mDocument.postKeyEvent(Document.KEY_EVENT_RELEASE, getCharCode(keyEvent), getKeyCode(keyEvent)); break; } } private void mouseButton(int type, PointF inDocument, int numberOfClicks, float zoomFactor) { int x = (int) pixelToTwip(inDocument.x, mDPI); int y = (int) pixelToTwip(inDocument.y, mDPI); mDocument.setClientZoom(TILE_SIZE, TILE_SIZE, (int) (mTileWidth / zoomFactor), (int) (mTileHeight / zoomFactor)); mDocument.postMouseEvent(type, x, y, numberOfClicks, Document.MOUSE_BUTTON_LEFT, Document.KEYBOARD_MODIFIER_NONE); } /** * @see TileProvider#mouseButtonDown(android.graphics.PointF, int, float) */ @Override public void mouseButtonDown(PointF documentCoordinate, int numberOfClicks, float zoomFactor) { mouseButton(Document.MOUSE_EVENT_BUTTON_DOWN, documentCoordinate, numberOfClicks, zoomFactor); } /** * @see TileProvider#mouseButtonUp(android.graphics.PointF, int, float) */ @Override public void mouseButtonUp(PointF documentCoordinate, int numberOfClicks, float zoomFactor) { mouseButton(Document.MOUSE_EVENT_BUTTON_UP, documentCoordinate, numberOfClicks, zoomFactor); } /** * @param command UNO command string * @param arguments Arguments to UNO command */ @Override public void postUnoCommand(String command, String arguments) { postUnoCommand(command, arguments, false); } /** * @param command * @param arguments * @param notifyWhenFinished */ @Override public void postUnoCommand(String command, String arguments, boolean notifyWhenFinished) { mDocument.postUnoCommand(command, arguments, notifyWhenFinished); } private void setTextSelection(int type, PointF documentCoordinate) { int x = (int) pixelToTwip(documentCoordinate.x, mDPI); int y = (int) pixelToTwip(documentCoordinate.y, mDPI); mDocument.setTextSelection(type, x, y); } /** * @see TileProvider#setTextSelectionStart(android.graphics.PointF) */ @Override public void setTextSelectionStart(PointF documentCoordinate) { setTextSelection(Document.SET_TEXT_SELECTION_START, documentCoordinate); } /** * @see TileProvider#setTextSelectionEnd(android.graphics.PointF) */ @Override public void setTextSelectionEnd(PointF documentCoordinate) { setTextSelection(Document.SET_TEXT_SELECTION_END, documentCoordinate); } /** * @see TileProvider#setTextSelectionReset(android.graphics.PointF) */ @Override public void setTextSelectionReset(PointF documentCoordinate) { setTextSelection(Document.SET_TEXT_SELECTION_RESET, documentCoordinate); } /** * @param mimeType * @return */ @Override public String getTextSelection(String mimeType) { return mDocument.getTextSelection(mimeType); } /** * paste * @param mimeType * @param data * @return */ @Override public boolean paste(String mimeType, String data) { return mDocument.paste(mimeType, data); } /** * @see org.libreoffice.TileProvider#setGraphicSelectionStart(android.graphics.PointF) */ @Override public void setGraphicSelectionStart(PointF documentCoordinate) { setGraphicSelection(Document.SET_GRAPHIC_SELECTION_START, documentCoordinate); } /** * @see org.libreoffice.TileProvider#setGraphicSelectionEnd(android.graphics.PointF) */ @Override public void setGraphicSelectionEnd(PointF documentCoordinate) { setGraphicSelection(Document.SET_GRAPHIC_SELECTION_END, documentCoordinate); } private void setGraphicSelection(int type, PointF documentCoordinate) { int x = (int) pixelToTwip(documentCoordinate.x, mDPI); int y = (int) pixelToTwip(documentCoordinate.y, mDPI); LibreOfficeMainActivity.setDocumentChanged(true); mDocument.setGraphicSelection(type, x, y); } @Override protected void finalize() throws Throwable { close(); super.finalize(); } /** * @see TileProvider#changePart(int) */ @Override public void changePart(int partIndex) { if (mDocument == null) return; mDocument.setPart(partIndex); resetDocumentSize(); } /** * @see TileProvider#getCurrentPartNumber() */ @Override public int getCurrentPartNumber() { if (mDocument == null) return 0; return mDocument.getPart(); } public void setDocumentPassword(String url, String password) { mOffice.setDocumentPassword(url, password); } public Document.MessageCallback getMessageCallback() { return mMessageCallback; } } // vim:set shiftwidth=4 softtabstop=4 expandtab: