1 /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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;
10 
11 import android.content.Context;
12 import android.graphics.Bitmap;
13 import android.graphics.PointF;
14 import android.os.Build;
15 import android.print.PrintAttributes;
16 import android.print.PrintDocumentAdapter;
17 import android.print.PrintManager;
18 import android.util.Log;
19 import android.view.KeyEvent;
20 import android.widget.Toast;
21 
22 import org.json.JSONException;
23 import org.json.JSONObject;
24 import org.libreoffice.kit.DirectBufferAllocator;
25 import org.libreoffice.kit.Document;
26 import org.libreoffice.kit.LibreOfficeKit;
27 import org.libreoffice.kit.Office;
28 import org.mozilla.gecko.gfx.BufferedCairoImage;
29 import org.mozilla.gecko.gfx.CairoImage;
30 import org.mozilla.gecko.gfx.IntSize;
31 
32 import java.io.File;
33 import java.nio.ByteBuffer;
34 
35 /**
36  * LOKit implementation of TileProvider.
37  */
38 class LOKitTileProvider implements TileProvider {
39     private static final String LOGTAG = LOKitTileProvider.class.getSimpleName();
40     private static final int TILE_SIZE = 256;
41     private final float mTileWidth;
42     private final float mTileHeight;
43     private String mInputFile;
44     private Office mOffice;
45     private Document mDocument;
46     private final boolean mIsReady;
47     private final LibreOfficeMainActivity mContext;
48 
49     private final float mDPI;
50     private float mWidthTwip;
51     private float mHeightTwip;
52 
53     private final Document.MessageCallback mMessageCallback;
54 
55     private final long objectCreationTime = System.currentTimeMillis();
56 
57     /**
58      * Initialize LOKit and load the document.
59      * @param messageCallback - callback for messages retrieved from LOKit
60      * @param input - input path of the document
61      */
LOKitTileProvider(LibreOfficeMainActivity context, InvalidationHandler messageCallback, String input)62     LOKitTileProvider(LibreOfficeMainActivity context, InvalidationHandler messageCallback, String input) {
63         mContext = context;
64         mMessageCallback = messageCallback;
65 
66         LibreOfficeKit.putenv("SAL_LOG=+WARN+INFO");
67         LibreOfficeKit.init(mContext);
68 
69         mOffice = new Office(LibreOfficeKit.getLibreOfficeKitHandle());
70         mOffice.setMessageCallback(messageCallback);
71         mOffice.setOptionalFeatures(Document.LOK_FEATURE_DOCUMENT_PASSWORD);
72         mContext.setTileProvider(this);
73         mInputFile = input;
74 
75         Log.i(LOGTAG, "====> Loading file '" + input + "'");
76 
77         File fileToBeEncoded = new File(input);
78         String encodedFileName = android.net.Uri.encode(fileToBeEncoded.getName());
79 
80         mDocument = mOffice.documentLoad(
81                 (new File(fileToBeEncoded.getParent(),encodedFileName)).getPath()
82         );
83 
84         if (mDocument == null && !mContext.isPasswordProtected()) {
85             Log.i(LOGTAG, "====> mOffice.documentLoad() returned null, trying to restart 'Office' and loading again");
86             mOffice.destroy();
87             Log.i(LOGTAG, "====> mOffice.destroy() done");
88             ByteBuffer handle = LibreOfficeKit.getLibreOfficeKitHandle();
89             Log.i(LOGTAG, "====> getLibreOfficeKitHandle() = " + handle);
90             mOffice = new Office(handle);
91             Log.i(LOGTAG, "====> new Office created");
92             mOffice.setMessageCallback(messageCallback);
93             mOffice.setOptionalFeatures(Document.LOK_FEATURE_DOCUMENT_PASSWORD);
94             Log.i(LOGTAG, "====> setup Lokit callback and optional features (password support)");
95             mDocument = mOffice.documentLoad(
96                     (new File(fileToBeEncoded.getParent(),encodedFileName)).getPath()
97             );
98         }
99 
100         Log.i(LOGTAG, "====> mDocument = " + mDocument);
101 
102         mDPI = LOKitShell.getDpi(mContext);
103         mTileWidth = pixelToTwip(TILE_SIZE, mDPI);
104         mTileHeight = pixelToTwip(TILE_SIZE, mDPI);
105 
106         if (mDocument != null)
107             mDocument.initializeForRendering();
108 
109         if (checkDocument()) {
110             postLoad();
111             mIsReady = true;
112         } else {
113             mIsReady = false;
114         }
115     }
116 
117     /**
118      * Triggered after the document is loaded.
119      */
postLoad()120     private void postLoad() {
121         mDocument.setMessageCallback(mMessageCallback);
122 
123         int parts = mDocument.getParts();
124         Log.i(LOGTAG, "Document parts: " + parts);
125         mContext.getDocumentPartView().clear();
126 
127         // Writer documents always have one part, so hide the navigation drawer.
128         if (mDocument.getDocumentType() != Document.DOCTYPE_TEXT) {
129             for (int i = 0; i < parts; i++) {
130                 String partName = mDocument.getPartName(i);
131                 if (partName.isEmpty()) {
132                     partName = getGenericPartName(i);
133                 }else if (partName.startsWith("Slide") || partName.startsWith("Sheet") || partName.startsWith("Part")) {
134                     partName = getGenericPartName(i);
135                 }
136                 Log.i(LOGTAG, "Document part " + i + " name:'" + partName + "'");
137 
138                 mDocument.setPart(i);
139                 resetDocumentSize();
140                 final DocumentPartView partView = new DocumentPartView(i, partName);
141                 mContext.getDocumentPartView().add(partView);
142             }
143         } else {
144             mContext.disableNavigationDrawer();
145             mContext.getToolbarController().hideItem(R.id.action_parts);
146         }
147 
148         // Enable headers for Calc documents
149         if (mDocument.getDocumentType() == Document.DOCTYPE_SPREADSHEET) {
150             mContext.initializeCalcHeaders();
151         }
152 
153         mDocument.setPart(0);
154 
155         setupDocumentFonts();
156 
157         LOKitShell.getMainHandler().post(new Runnable() {
158             @Override
159             public void run() {
160                 mContext.getDocumentPartViewListAdapter().notifyDataSetChanged();
161             }
162         });
163     }
164 
addPart()165     public void addPart(){
166         int parts = mDocument.getParts();
167         if(mDocument.getDocumentType() == Document.DOCTYPE_SPREADSHEET){
168             try{
169                 JSONObject jsonObject = new JSONObject();
170                 JSONObject values = new JSONObject();
171                 JSONObject values2 = new JSONObject();
172                 values.put("type", "long");
173                 values.put("value", 0); //add to the last
174                 values2.put("type", "string");
175                 values2.put("value", "");
176                 jsonObject.put("Name", values2);
177                 jsonObject.put("Index", values);
178                 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:Insert", jsonObject.toString()));
179             }catch (JSONException e) {
180                 e.printStackTrace();
181             }
182         } else if (mDocument.getDocumentType() == Document.DOCTYPE_PRESENTATION){
183             LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:InsertPage"));
184         }
185 
186         String partName = mDocument.getPartName(parts);
187         if (partName.isEmpty()) {
188             partName = getGenericPartName(parts);
189         }
190         mDocument.setPart(parts);
191         resetDocumentSize();
192         final DocumentPartView partView = new DocumentPartView(parts, partName);
193         mContext.getDocumentPartView().add(partView);
194     }
195 
resetParts()196     public void resetParts(){
197         int parts = mDocument.getParts();
198         mContext.getDocumentPartView().clear();
199         if (mDocument.getDocumentType() != Document.DOCTYPE_TEXT) {
200             for (int i = 0; i < parts; i++) {
201                 String partName = mDocument.getPartName(i);
202 
203                 if (partName.isEmpty()) {
204                     partName = getGenericPartName(i);
205                 }
206                 Log.i(LOGTAG, "resetParts: " + partName);
207                 mDocument.setPart(i);
208                 resetDocumentSize();
209                 final DocumentPartView partView = new DocumentPartView(i, partName);
210                 mContext.getDocumentPartView().add(partView);
211             }
212         }
213     } public void renamePart(String partName) {
214         try{
215             for(int i=0; i<mDocument.getParts(); i++){
216                 if(mContext.getDocumentPartView().get(i).partName.equals(partName)){
217                     //part name must be unique
218                     Toast.makeText(mContext, mContext.getString(R.string.name_already_used), Toast.LENGTH_SHORT).show();
219                     return;
220                 }
221             }
222             JSONObject parameter = new JSONObject();
223             JSONObject name = new JSONObject();
224             name.put("type", "string");
225             name.put("value", partName);
226             parameter.put("Name", name);
227             if(isPresentation()){
228                 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND_NOTIFY, ".uno:RenamePage", parameter.toString(),true));
229             }else {
230                 JSONObject index = new JSONObject();
231                 index.put("type","long");
232                 index.put("value", getCurrentPartNumber()+1);
233                 parameter.put("Index", index);
234                 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND_NOTIFY, ".uno:Name", parameter.toString(),true));
235             }
236         }catch (JSONException e){
237             e.printStackTrace();
238         }
239     }
240 
removePart()241     public void removePart() {
242         try{
243             if (!isSpreadsheet() && !isPresentation()) {
244                 //document must be spreadsheet or presentation
245                 return;
246             }
247 
248             if(isPresentation()){
249                 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND_NOTIFY, ".uno:DeletePage", true));
250                 return;
251             }
252 
253             if(getPartsCount() < 2){
254                 return;
255             }
256 
257             JSONObject parameter = new JSONObject();
258             JSONObject index = new JSONObject();
259             index.put("type","long");
260             index.put("value", getCurrentPartNumber()+1);
261             parameter.put("Index", index);
262             LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND_NOTIFY, ".uno:Remove", parameter.toString(),true));
263         }catch (JSONException e){
264             e.printStackTrace();
265         }
266     }
267 
268     @Override
saveDocumentAs(final String filePath, String format, boolean takeOwnership)269     public boolean saveDocumentAs(final String filePath, String format, boolean takeOwnership) {
270         String options = "";
271         if (takeOwnership) {
272             options = "TakeOwnership";
273         }
274 
275         final String newFilePath = "file://" + filePath;
276         Log.d("saveFilePathURL", newFilePath);
277         LOKitShell.showProgressSpinner(mContext);
278         mDocument.saveAs(newFilePath, format, options);
279         final boolean ok;
280         if (!mOffice.getError().isEmpty()){
281             ok = true;
282             Log.e("Save Error", mOffice.getError());
283             if (format.equals("svg")) {
284                 // error in creating temp slideshow svg file
285                 Log.d(LOGTAG, "Error in creating temp slideshow svg file");
286             } else if(format.equals("pdf")){
287                 Log.d(LOGTAG, "Error in creating pdf file");
288             } else {
289                 LOKitShell.getMainHandler().post(new Runnable() {
290                     @Override
291                     public void run() {
292                         // There was some error
293                         mContext.showCustomStatusMessage(mContext.getString(R.string.unable_to_save));
294                     }
295                 });
296             }
297         } else {
298             ok = false;
299             if (format.equals("svg")) {
300                 // successfully created temp slideshow svg file
301                 LOKitShell.getMainHandler().post(new Runnable() {
302                     @Override
303                     public void run() {
304                         mContext.startPresentation(newFilePath);
305                     }
306                 });
307             } else if (takeOwnership) {
308                 mInputFile = filePath;
309             }
310         }
311         LOKitShell.hideProgressSpinner(mContext);
312         return ok;
313     }
314 
315     @Override
saveDocumentAs(final String filePath, boolean takeOwnership)316     public boolean saveDocumentAs(final String filePath, boolean takeOwnership) {
317         final int docType = mDocument.getDocumentType();
318         if (docType == Document.DOCTYPE_TEXT)
319             return saveDocumentAs(filePath, "odt", takeOwnership);
320         else if (docType == Document.DOCTYPE_SPREADSHEET)
321             return saveDocumentAs(filePath, "ods", takeOwnership);
322         else if (docType == Document.DOCTYPE_PRESENTATION)
323             return saveDocumentAs(filePath, "odp", takeOwnership);
324         else if (docType == Document.DOCTYPE_DRAWING)
325             return saveDocumentAs(filePath, "odg", takeOwnership);
326 
327         Log.w(LOGTAG, "Cannot determine file format from document. Not saving.");
328         return false;
329     }
330 
printDocument()331     public void printDocument() {
332         if (Build.VERSION.SDK_INT < 19) {
333             mContext.showCustomStatusMessage(mContext.getString(R.string.printing_not_supported));
334             return;
335         }
336 
337         String mInputFileName = (new File(mInputFile)).getName();
338         String file = mInputFileName.substring(0,(mInputFileName.length()-3))+"pdf";
339         String cacheFile = mContext.getExternalCacheDir().getAbsolutePath() + "/" + file;
340         mDocument.saveAs("file://"+cacheFile,"pdf","");
341         try {
342             PrintManager printManager = (PrintManager) mContext.getSystemService(Context.PRINT_SERVICE);
343             PrintDocumentAdapter printAdapter = new PDFDocumentAdapter(mContext, cacheFile);
344             printManager.print("Document", printAdapter, new PrintAttributes.Builder().build());
345 
346         } catch (Exception e) {
347             e.printStackTrace();
348         }
349     }
350 
saveDocument()351     public void saveDocument(){
352         mContext.saveDocument();
353     }
354 
setupDocumentFonts()355     private void setupDocumentFonts() {
356         String values = mDocument.getCommandValues(".uno:CharFontName");
357         if (values == null || values.isEmpty())
358             return;
359 
360         mContext.getFontController().parseJson(values);
361         mContext.getFontController().setupFontViews();
362     }
363 
getGenericPartName(int i)364     private String getGenericPartName(int i) {
365         if (mDocument == null) {
366             return "";
367         }
368         switch (mDocument.getDocumentType()) {
369             case Document.DOCTYPE_DRAWING:
370             case Document.DOCTYPE_TEXT:
371                 return mContext.getString(R.string.page) + " " + (i + 1);
372             case Document.DOCTYPE_SPREADSHEET:
373                 return mContext.getString(R.string.sheet) + " " + (i + 1);
374             case Document.DOCTYPE_PRESENTATION:
375                 return mContext.getString(R.string.slide) + " " + (i + 1);
376             case Document.DOCTYPE_OTHER:
377             default:
378                 return mContext.getString(R.string.part) + " " + (i + 1);
379         }
380     }
381 
twipToPixel(float input, float dpi)382     static float twipToPixel(float input, float dpi) {
383         return input / 1440.0f * dpi;
384     }
385 
pixelToTwip(float input, float dpi)386     private static float pixelToTwip(float input, float dpi) {
387         return (input / dpi) * 1440.0f;
388     }
389 
390 
391     /**
392      * @see TileProvider#getPartsCount()
393      */
394     @Override
getPartsCount()395     public int getPartsCount() {
396         return mDocument.getParts();
397     }
398 
399     /**
400      * Wrapper for getPartPageRectangles() JNI function.
401      */
getPartPageRectangles()402     public String getPartPageRectangles() {
403         return mDocument.getPartPageRectangles();
404     }
405 
406     /**
407      * Fetch Calc header information.
408      */
getCalcHeaders()409     public String getCalcHeaders() {
410         long nX = 0;
411         long nY = 0;
412         long nWidth = mDocument.getDocumentWidth();
413         long nHeight = mDocument.getDocumentHeight();
414         return mDocument.getCommandValues(".uno:ViewRowColumnHeaders?x=" + nX + "&y=" + nY
415                 + "&width=" + nWidth + "&height=" + nHeight);
416     }
417 
418     /**
419      * @see TileProvider#onSwipeLeft()
420      */
421     @Override
onSwipeLeft()422     public void onSwipeLeft() {
423         if (mDocument.getDocumentType() == Document.DOCTYPE_PRESENTATION &&
424                 getCurrentPartNumber() < getPartsCount()-1) {
425             LOKitShell.sendChangePartEvent(getCurrentPartNumber()+1);
426         }
427     }
428 
429     /**
430      * @see TileProvider#onSwipeRight()
431      */
432     @Override
onSwipeRight()433     public void onSwipeRight() {
434         if (mDocument.getDocumentType() == Document.DOCTYPE_PRESENTATION &&
435                 getCurrentPartNumber() > 0) {
436             LOKitShell.sendChangePartEvent(getCurrentPartNumber()-1);
437         }
438     }
439 
checkDocument()440     private boolean checkDocument() {
441         String error = null;
442         boolean ret;
443 
444         if (mDocument == null || !mOffice.getError().isEmpty()) {
445             error = "Cannot open " + mInputFile + ": " + mOffice.getError();
446             ret = false;
447         } else {
448             ret = resetDocumentSize();
449             if (!ret) {
450                 error = "Document returned an invalid size or the document is empty.";
451             }
452         }
453 
454         if (!ret && !mContext.isPasswordProtected()) {
455             final String message = error;
456             LOKitShell.getMainHandler().post(new Runnable() {
457                 @Override
458                 public void run() {
459                     mContext.showAlertDialog(message);
460                 }
461             });
462         } else if (!ret && mContext.isPasswordProtected()) {
463             mContext.finish();
464         }
465 
466         return ret;
467     }
468 
resetDocumentSize()469     private boolean resetDocumentSize() {
470         mWidthTwip = mDocument.getDocumentWidth();
471         mHeightTwip = mDocument.getDocumentHeight();
472 
473         if (mWidthTwip == 0 || mHeightTwip == 0) {
474             Log.e(LOGTAG, "Document size zero - last error: " + mOffice.getError());
475             return false;
476         } else {
477             Log.i(LOGTAG, "Reset document size: " + mDocument.getDocumentWidth() + " x " + mDocument.getDocumentHeight());
478         }
479 
480         return true;
481     }
482 
483     @Override
setDocumentSize(int pageWidth, int pageHeight)484     public void setDocumentSize(int pageWidth, int pageHeight){
485         mWidthTwip = pageWidth;
486         mHeightTwip = pageHeight;
487     }
488 
489     /**
490      * @see TileProvider#getPageWidth()
491      */
492     @Override
getPageWidth()493     public int getPageWidth() {
494         return (int) twipToPixel(mWidthTwip, mDPI);
495     }
496 
497     /**
498      * @see TileProvider#getPageHeight()
499      */
500     @Override
getPageHeight()501     public int getPageHeight() {
502         return (int) twipToPixel(mHeightTwip, mDPI);
503     }
504 
505     /**
506      * @see TileProvider#isReady()
507      */
508     @Override
isReady()509     public boolean isReady() {
510         return mIsReady;
511     }
512 
513     /**
514      * @see TileProvider#createTile(float, float, org.mozilla.gecko.gfx.IntSize, float)
515      */
516     @Override
createTile(float x, float y, IntSize tileSize, float zoom)517     public CairoImage createTile(float x, float y, IntSize tileSize, float zoom) {
518         ByteBuffer buffer = DirectBufferAllocator.guardedAllocate(tileSize.width * tileSize.height * 4);
519         if (buffer == null)
520             return null;
521 
522         CairoImage image = new BufferedCairoImage(buffer, tileSize.width, tileSize.height, CairoImage.FORMAT_ARGB32);
523         rerenderTile(image, x, y, tileSize, zoom);
524         return image;
525     }
526 
527     /**
528      * @see TileProvider#rerenderTile(org.mozilla.gecko.gfx.CairoImage, float, float, org.mozilla.gecko.gfx.IntSize, float)
529      */
530     @Override
rerenderTile(CairoImage image, float x, float y, IntSize tileSize, float zoom)531     public void rerenderTile(CairoImage image, float x, float y, IntSize tileSize, float zoom) {
532         if (mDocument != null && image.getBuffer() != null) {
533             float twipX = pixelToTwip(x, mDPI) / zoom;
534             float twipY = pixelToTwip(y, mDPI) / zoom;
535             float twipWidth = mTileWidth / zoom;
536             float twipHeight = mTileHeight / zoom;
537             long start = System.currentTimeMillis() - objectCreationTime;
538 
539             //Log.i(LOGTAG, "paintTile >> @" + start + " (" + tileSize.width + " " + tileSize.height + " " + (int) twipX + " " + (int) twipY + " " + (int) twipWidth + " " + (int) twipHeight + ")");
540             mDocument.paintTile(image.getBuffer(), tileSize.width, tileSize.height, (int) twipX, (int) twipY, (int) twipWidth, (int) twipHeight);
541 
542             long stop = System.currentTimeMillis() - objectCreationTime;
543             //Log.i(LOGTAG, "paintTile << @" + stop + " elapsed: " + (stop - start));
544         } else {
545             if (mDocument == null) {
546                 Log.e(LOGTAG, "Document is null!!");
547             }
548         }
549     }
550 
551     /**
552      * @see TileProvider#thumbnail(int)
553      */
554     @Override
thumbnail(int size)555     public Bitmap thumbnail(int size) {
556         int widthPixel = getPageWidth();
557         int heightPixel = getPageHeight();
558 
559         if (widthPixel > heightPixel) {
560             double ratio = heightPixel / (double) widthPixel;
561             widthPixel = size;
562             heightPixel = (int) (widthPixel * ratio);
563         } else {
564             double ratio = widthPixel / (double) heightPixel;
565             heightPixel = size;
566             widthPixel = (int) (heightPixel * ratio);
567         }
568 
569         Log.w(LOGTAG, "Thumbnail size: " + getPageWidth() + " " + getPageHeight() + " " + widthPixel + " " + heightPixel);
570 
571         ByteBuffer buffer = ByteBuffer.allocateDirect(widthPixel * heightPixel * 4);
572         if (mDocument != null)
573             mDocument.paintTile(buffer, widthPixel, heightPixel, 0, 0, (int) mWidthTwip, (int) mHeightTwip);
574 
575         Bitmap bitmap = null;
576         try {
577             bitmap = Bitmap.createBitmap(widthPixel, heightPixel, Bitmap.Config.ARGB_8888);
578             bitmap.copyPixelsFromBuffer(buffer);
579         } catch (IllegalArgumentException e) {
580             Log.e(LOGTAG, "width (" + widthPixel + ") and height (" + heightPixel + ") must not be 0! (ToDo: likely timing issue)");
581         }
582         if (bitmap == null) {
583             Log.w(LOGTAG, "Thumbnail not created!");
584         }
585         return bitmap;
586     }
587 
588     /**
589      * @see TileProvider#close()
590      */
591     @Override
close()592     public void close() {
593         Log.i(LOGTAG, "Document destroyed: " + mInputFile);
594         if (mDocument != null) {
595             mDocument.destroy();
596             mDocument = null;
597         }
598     }
599 
600     /**
601      * @see TileProvider#isDrawing()
602      */
603     @Override
isDrawing()604     public boolean isDrawing() {
605         return mDocument != null && mDocument.getDocumentType() == Document.DOCTYPE_DRAWING;
606     }
607 
608     /**
609      * @see TileProvider#isTextDocument()
610      */
611     @Override
isTextDocument()612     public boolean isTextDocument() {
613         return mDocument != null && mDocument.getDocumentType() == Document.DOCTYPE_TEXT;
614     }
615 
616     /**
617      * @see TileProvider#isSpreadsheet()
618      */
619     @Override
isSpreadsheet()620     public boolean isSpreadsheet() {
621         return mDocument != null && mDocument.getDocumentType() == Document.DOCTYPE_SPREADSHEET;
622     }
623 
624     /**
625      * @see TileProvider#isPresentation()
626      */
627     @Override
isPresentation()628     public boolean isPresentation(){
629         return mDocument != null && mDocument.getDocumentType() == Document.DOCTYPE_PRESENTATION;
630     }
631 
632     /**
633      * Returns the Unicode character generated by this event or 0.
634      */
getCharCode(KeyEvent keyEvent)635     private int getCharCode(KeyEvent keyEvent) {
636         switch (keyEvent.getKeyCode())
637         {
638             case KeyEvent.KEYCODE_DEL:
639             case KeyEvent.KEYCODE_ENTER:
640                 return 0;
641         }
642         return keyEvent.getUnicodeChar();
643     }
644 
645     /**
646      * Returns the integer code representing the key of the event (non-zero for
647      * control keys).
648      */
getKeyCode(KeyEvent keyEvent)649     private int getKeyCode(KeyEvent keyEvent) {
650         switch (keyEvent.getKeyCode()) {
651             case KeyEvent.KEYCODE_DEL:
652                 return com.sun.star.awt.Key.BACKSPACE;
653             case KeyEvent.KEYCODE_ENTER:
654                 return com.sun.star.awt.Key.RETURN;
655         }
656         return 0;
657     }
658 
659     /**
660      * @see TileProvider#sendKeyEvent(android.view.KeyEvent)
661      */
662     @Override
sendKeyEvent(KeyEvent keyEvent)663     public void sendKeyEvent(KeyEvent keyEvent) {
664         switch (keyEvent.getAction()) {
665             case KeyEvent.ACTION_MULTIPLE:
666                 String keyString = keyEvent.getCharacters();
667                 for (int i = 0; i < keyString.length(); i++) {
668                     int codePoint = keyString.codePointAt(i);
669                     mDocument.postKeyEvent(Document.KEY_EVENT_PRESS, codePoint, getKeyCode(keyEvent));
670                 }
671                 break;
672             case KeyEvent.ACTION_DOWN:
673                 mDocument.postKeyEvent(Document.KEY_EVENT_PRESS, getCharCode(keyEvent), getKeyCode(keyEvent));
674                 break;
675             case KeyEvent.ACTION_UP:
676                 mDocument.postKeyEvent(Document.KEY_EVENT_RELEASE, getCharCode(keyEvent), getKeyCode(keyEvent));
677                 break;
678         }
679     }
680 
mouseButton(int type, PointF inDocument, int numberOfClicks, float zoomFactor)681     private void mouseButton(int type, PointF inDocument, int numberOfClicks, float zoomFactor) {
682         int x = (int) pixelToTwip(inDocument.x, mDPI);
683         int y = (int) pixelToTwip(inDocument.y, mDPI);
684 
685         mDocument.setClientZoom(TILE_SIZE, TILE_SIZE, (int) (mTileWidth / zoomFactor), (int) (mTileHeight / zoomFactor));
686         mDocument.postMouseEvent(type, x, y, numberOfClicks, Document.MOUSE_BUTTON_LEFT, Document.KEYBOARD_MODIFIER_NONE);
687     }
688 
689     /**
690      * @see TileProvider#mouseButtonDown(android.graphics.PointF, int, float)
691      */
692     @Override
mouseButtonDown(PointF documentCoordinate, int numberOfClicks, float zoomFactor)693     public void mouseButtonDown(PointF documentCoordinate, int numberOfClicks, float zoomFactor) {
694         mouseButton(Document.MOUSE_EVENT_BUTTON_DOWN, documentCoordinate, numberOfClicks, zoomFactor);
695     }
696 
697     /**
698      * @see TileProvider#mouseButtonUp(android.graphics.PointF, int, float)
699      */
700     @Override
mouseButtonUp(PointF documentCoordinate, int numberOfClicks, float zoomFactor)701     public void mouseButtonUp(PointF documentCoordinate, int numberOfClicks, float zoomFactor) {
702         mouseButton(Document.MOUSE_EVENT_BUTTON_UP, documentCoordinate, numberOfClicks, zoomFactor);
703     }
704 
705     /**
706      * @param command   UNO command string
707      * @param arguments Arguments to UNO command
708      */
709     @Override
postUnoCommand(String command, String arguments)710     public void postUnoCommand(String command, String arguments) {
711         postUnoCommand(command, arguments, false);
712     }
713 
714     /**
715      * @param command
716      * @param arguments
717      * @param notifyWhenFinished
718      */
719     @Override
postUnoCommand(String command, String arguments, boolean notifyWhenFinished)720     public void postUnoCommand(String command, String arguments, boolean notifyWhenFinished) {
721         mDocument.postUnoCommand(command, arguments, notifyWhenFinished);
722     }
723 
setTextSelection(int type, PointF documentCoordinate)724     private void setTextSelection(int type, PointF documentCoordinate) {
725         int x = (int) pixelToTwip(documentCoordinate.x, mDPI);
726         int y = (int) pixelToTwip(documentCoordinate.y, mDPI);
727         mDocument.setTextSelection(type, x, y);
728     }
729 
730     /**
731      * @see TileProvider#setTextSelectionStart(android.graphics.PointF)
732      */
733     @Override
setTextSelectionStart(PointF documentCoordinate)734     public void setTextSelectionStart(PointF documentCoordinate) {
735         setTextSelection(Document.SET_TEXT_SELECTION_START, documentCoordinate);
736     }
737 
738     /**
739      * @see TileProvider#setTextSelectionEnd(android.graphics.PointF)
740      */
741     @Override
setTextSelectionEnd(PointF documentCoordinate)742     public void setTextSelectionEnd(PointF documentCoordinate) {
743         setTextSelection(Document.SET_TEXT_SELECTION_END, documentCoordinate);
744     }
745 
746     /**
747      * @see TileProvider#setTextSelectionReset(android.graphics.PointF)
748      */
749     @Override
setTextSelectionReset(PointF documentCoordinate)750     public void setTextSelectionReset(PointF documentCoordinate) {
751         setTextSelection(Document.SET_TEXT_SELECTION_RESET, documentCoordinate);
752     }
753 
754     /**
755      * @param mimeType
756      * @return
757      */
758     @Override
getTextSelection(String mimeType)759     public String getTextSelection(String mimeType) {
760         return mDocument.getTextSelection(mimeType);
761     }
762 
763     /**
764      * paste
765      * @param mimeType
766      * @param data
767      * @return
768      */
769     @Override
paste(String mimeType, String data)770     public boolean paste(String mimeType, String data) {
771         return mDocument.paste(mimeType, data);
772     }
773 
774 
775     /**
776      * @see org.libreoffice.TileProvider#setGraphicSelectionStart(android.graphics.PointF)
777      */
778     @Override
setGraphicSelectionStart(PointF documentCoordinate)779     public void setGraphicSelectionStart(PointF documentCoordinate) {
780         setGraphicSelection(Document.SET_GRAPHIC_SELECTION_START, documentCoordinate);
781     }
782 
783     /**
784      * @see org.libreoffice.TileProvider#setGraphicSelectionEnd(android.graphics.PointF)
785      */
786     @Override
setGraphicSelectionEnd(PointF documentCoordinate)787     public void setGraphicSelectionEnd(PointF documentCoordinate) {
788         setGraphicSelection(Document.SET_GRAPHIC_SELECTION_END, documentCoordinate);
789     }
790 
setGraphicSelection(int type, PointF documentCoordinate)791     private void setGraphicSelection(int type, PointF documentCoordinate) {
792         int x = (int) pixelToTwip(documentCoordinate.x, mDPI);
793         int y = (int) pixelToTwip(documentCoordinate.y, mDPI);
794         LibreOfficeMainActivity.setDocumentChanged(true);
795         mDocument.setGraphicSelection(type, x, y);
796     }
797 
798     @Override
finalize()799     protected void finalize() throws Throwable {
800         close();
801         super.finalize();
802     }
803 
804     /**
805      * @see TileProvider#changePart(int)
806      */
807     @Override
changePart(int partIndex)808     public void changePart(int partIndex) {
809         if (mDocument == null)
810             return;
811 
812         mDocument.setPart(partIndex);
813         resetDocumentSize();
814     }
815 
816     /**
817      * @see TileProvider#getCurrentPartNumber()
818      */
819     @Override
getCurrentPartNumber()820     public int getCurrentPartNumber() {
821         if (mDocument == null)
822             return 0;
823 
824         return mDocument.getPart();
825     }
826 
setDocumentPassword(String url, String password)827     public void setDocumentPassword(String url, String password) {
828         mOffice.setDocumentPassword(url, password);
829     }
830 
getMessageCallback()831     public Document.MessageCallback getMessageCallback() {
832         return mMessageCallback;
833     }
834 }
835 
836 // vim:set shiftwidth=4 softtabstop=4 expandtab:
837