diff options
author | Tor Andersson <tor.andersson@artifex.com> | 2016-03-30 17:49:04 +0200 |
---|---|---|
committer | Tor Andersson <tor.andersson@artifex.com> | 2016-03-31 13:00:41 +0200 |
commit | d68576c3785572c1f5d41f83015b8fe6bbcbe9e8 (patch) | |
tree | 431a86edfac640864ba7f406611e8fe9929908cd /platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageView.java | |
parent | 32cdb2246eeb9e8109a712ec2a5dd2938e30e9b6 (diff) | |
download | mupdf-d68576c3785572c1f5d41f83015b8fe6bbcbe9e8.tar.xz |
Reorganize java and android source.
platform/java and platform/android are reorganized:
platform/java
The new JNI Java classes, mupdf_native.{c,h}, Makefile and Makejar.
platform/java/example
The example desktop viewer classes.
platform/android/viewer
The original demo viewer.
ndk-build is used to build libmupdf_java.so,
making reference to mupdf_native.{c,h} in platform/java.
Diffstat (limited to 'platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageView.java')
-rw-r--r-- | platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageView.java | 692 |
1 files changed, 692 insertions, 0 deletions
diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageView.java b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageView.java new file mode 100644 index 00000000..ff6b6bbb --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageView.java @@ -0,0 +1,692 @@ +package com.artifex.mupdfdemo; + +import java.util.ArrayList; + +import com.artifex.mupdfdemo.MuPDFCore.Cookie; + +import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.content.ClipData; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.RectF; +import android.net.Uri; +import android.os.Build; +import android.text.method.PasswordTransformationMethod; +import android.view.LayoutInflater; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; + +/* This enum should be kept in line with the cooresponding C enum in mupdf.c */ +enum SignatureState { + NoSupport, + Unsigned, + Signed +} + +abstract class PassClickResultVisitor { + public abstract void visitText(PassClickResultText result); + public abstract void visitChoice(PassClickResultChoice result); + public abstract void visitSignature(PassClickResultSignature result); +} + +class PassClickResult { + public final boolean changed; + + public PassClickResult(boolean _changed) { + changed = _changed; + } + + public void acceptVisitor(PassClickResultVisitor visitor) { + } +} + +class PassClickResultText extends PassClickResult { + public final String text; + + public PassClickResultText(boolean _changed, String _text) { + super(_changed); + text = _text; + } + + public void acceptVisitor(PassClickResultVisitor visitor) { + visitor.visitText(this); + } +} + +class PassClickResultChoice extends PassClickResult { + public final String [] options; + public final String [] selected; + + public PassClickResultChoice(boolean _changed, String [] _options, String [] _selected) { + super(_changed); + options = _options; + selected = _selected; + } + + public void acceptVisitor(PassClickResultVisitor visitor) { + visitor.visitChoice(this); + } +} + +class PassClickResultSignature extends PassClickResult { + public final SignatureState state; + + public PassClickResultSignature(boolean _changed, int _state) { + super(_changed); + state = SignatureState.values()[_state]; + } + + public void acceptVisitor(PassClickResultVisitor visitor) { + visitor.visitSignature(this); + } +} + +public class MuPDFPageView extends PageView implements MuPDFView { + final private FilePicker.FilePickerSupport mFilePickerSupport; + private final MuPDFCore mCore; + private AsyncTask<Void,Void,PassClickResult> mPassClick; + private RectF mWidgetAreas[]; + private Annotation mAnnotations[]; + private int mSelectedAnnotationIndex = -1; + private AsyncTask<Void,Void,RectF[]> mLoadWidgetAreas; + private AsyncTask<Void,Void,Annotation[]> mLoadAnnotations; + private AlertDialog.Builder mTextEntryBuilder; + private AlertDialog.Builder mChoiceEntryBuilder; + private AlertDialog.Builder mSigningDialogBuilder; + private AlertDialog.Builder mSignatureReportBuilder; + private AlertDialog.Builder mPasswordEntryBuilder; + private EditText mPasswordText; + private AlertDialog mTextEntry; + private AlertDialog mPasswordEntry; + private EditText mEditText; + private AsyncTask<String,Void,Boolean> mSetWidgetText; + private AsyncTask<String,Void,Void> mSetWidgetChoice; + private AsyncTask<PointF[],Void,Void> mAddStrikeOut; + private AsyncTask<PointF[][],Void,Void> mAddInk; + private AsyncTask<Integer,Void,Void> mDeleteAnnotation; + private AsyncTask<Void,Void,String> mCheckSignature; + private AsyncTask<Void,Void,Boolean> mSign; + private Runnable changeReporter; + + public MuPDFPageView(Context c, FilePicker.FilePickerSupport filePickerSupport, MuPDFCore core, Point parentSize, Bitmap sharedHqBm) { + super(c, parentSize, sharedHqBm); + mFilePickerSupport = filePickerSupport; + mCore = core; + mTextEntryBuilder = new AlertDialog.Builder(c); + mTextEntryBuilder.setTitle(getContext().getString(R.string.fill_out_text_field)); + LayoutInflater inflater = (LayoutInflater)c.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mEditText = (EditText)inflater.inflate(R.layout.textentry, null); + mTextEntryBuilder.setView(mEditText); + mTextEntryBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + mTextEntryBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mSetWidgetText = new AsyncTask<String,Void,Boolean> () { + @Override + protected Boolean doInBackground(String... arg0) { + return mCore.setFocusedWidgetText(mPageNumber, arg0[0]); + } + @Override + protected void onPostExecute(Boolean result) { + changeReporter.run(); + if (!result) + invokeTextDialog(mEditText.getText().toString()); + } + }; + + mSetWidgetText.execute(mEditText.getText().toString()); + } + }); + mTextEntry = mTextEntryBuilder.create(); + + mChoiceEntryBuilder = new AlertDialog.Builder(c); + mChoiceEntryBuilder.setTitle(getContext().getString(R.string.choose_value)); + + mSigningDialogBuilder = new AlertDialog.Builder(c); + mSigningDialogBuilder.setTitle("Select certificate and sign?"); + mSigningDialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + mSigningDialogBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + FilePicker picker = new FilePicker(mFilePickerSupport) { + @Override + void onPick(Uri uri) { + signWithKeyFile(uri); + } + }; + + picker.pick(); + } + }); + + mSignatureReportBuilder = new AlertDialog.Builder(c); + mSignatureReportBuilder.setTitle("Signature checked"); + mSignatureReportBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + + mPasswordText = new EditText(c); + mPasswordText.setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); + mPasswordText.setTransformationMethod(new PasswordTransformationMethod()); + + mPasswordEntryBuilder = new AlertDialog.Builder(c); + mPasswordEntryBuilder.setTitle(R.string.enter_password); + mPasswordEntryBuilder.setView(mPasswordText); + mPasswordEntryBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + + mPasswordEntry = mPasswordEntryBuilder.create(); + } + + private void signWithKeyFile(final Uri uri) { + mPasswordEntry.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + mPasswordEntry.setButton(AlertDialog.BUTTON_POSITIVE, "Sign", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + signWithKeyFileAndPassword(uri, mPasswordText.getText().toString()); + } + }); + + mPasswordEntry.show(); + } + + private void signWithKeyFileAndPassword(final Uri uri, final String password) { + mSign = new AsyncTask<Void,Void,Boolean>() { + @Override + protected Boolean doInBackground(Void... params) { + return mCore.signFocusedSignature(Uri.decode(uri.getEncodedPath()), password); + } + @Override + protected void onPostExecute(Boolean result) { + if (result) + { + changeReporter.run(); + } + else + { + mPasswordText.setText(""); + signWithKeyFile(uri); + } + } + + }; + + mSign.execute(); + } + + public LinkInfo hitLink(float x, float y) { + // Since link highlighting was implemented, the super class + // PageView has had sufficient information to be able to + // perform this method directly. Making that change would + // make MuPDFCore.hitLinkPage superfluous. + float scale = mSourceScale*(float)getWidth()/(float)mSize.x; + float docRelX = (x - getLeft())/scale; + float docRelY = (y - getTop())/scale; + + for (LinkInfo l: mLinks) + if (l.rect.contains(docRelX, docRelY)) + return l; + + return null; + } + + private void invokeTextDialog(String text) { + mEditText.setText(text); + mTextEntry.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + mTextEntry.show(); + } + + private void invokeChoiceDialog(final String [] options) { + mChoiceEntryBuilder.setItems(options, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mSetWidgetChoice = new AsyncTask<String,Void,Void>() { + @Override + protected Void doInBackground(String... params) { + String [] sel = {params[0]}; + mCore.setFocusedWidgetChoiceSelected(sel); + return null; + } + + @Override + protected void onPostExecute(Void result) { + changeReporter.run(); + } + }; + + mSetWidgetChoice.execute(options[which]); + } + }); + AlertDialog dialog = mChoiceEntryBuilder.create(); + dialog.show(); + } + + private void invokeSignatureCheckingDialog() { + mCheckSignature = new AsyncTask<Void,Void,String> () { + @Override + protected String doInBackground(Void... params) { + return mCore.checkFocusedSignature(); + } + @Override + protected void onPostExecute(String result) { + AlertDialog report = mSignatureReportBuilder.create(); + report.setMessage(result); + report.show(); + } + }; + + mCheckSignature.execute(); + } + + private void invokeSigningDialog() { + AlertDialog dialog = mSigningDialogBuilder.create(); + dialog.show(); + } + + private void warnNoSignatureSupport() { + AlertDialog dialog = mSignatureReportBuilder.create(); + dialog.setTitle("App built with no signature support"); + dialog.show(); + } + + public void setChangeReporter(Runnable reporter) { + changeReporter = reporter; + } + + public Hit passClickEvent(float x, float y) { + float scale = mSourceScale*(float)getWidth()/(float)mSize.x; + final float docRelX = (x - getLeft())/scale; + final float docRelY = (y - getTop())/scale; + boolean hit = false; + int i; + + if (mAnnotations != null) { + for (i = 0; i < mAnnotations.length; i++) + if (mAnnotations[i].contains(docRelX, docRelY)) { + hit = true; + break; + } + + if (hit) { + switch (mAnnotations[i].type) { + case HIGHLIGHT: + case UNDERLINE: + case SQUIGGLY: + case STRIKEOUT: + case INK: + mSelectedAnnotationIndex = i; + setItemSelectBox(mAnnotations[i]); + return Hit.Annotation; + } + } + } + + mSelectedAnnotationIndex = -1; + setItemSelectBox(null); + + if (!mCore.javascriptSupported()) + return Hit.Nothing; + + if (mWidgetAreas != null) { + for (i = 0; i < mWidgetAreas.length && !hit; i++) + if (mWidgetAreas[i].contains(docRelX, docRelY)) + hit = true; + } + + if (hit) { + mPassClick = new AsyncTask<Void,Void,PassClickResult>() { + @Override + protected PassClickResult doInBackground(Void... arg0) { + return mCore.passClickEvent(mPageNumber, docRelX, docRelY); + } + + @Override + protected void onPostExecute(PassClickResult result) { + if (result.changed) { + changeReporter.run(); + } + + result.acceptVisitor(new PassClickResultVisitor() { + @Override + public void visitText(PassClickResultText result) { + invokeTextDialog(result.text); + } + + @Override + public void visitChoice(PassClickResultChoice result) { + invokeChoiceDialog(result.options); + } + + @Override + public void visitSignature(PassClickResultSignature result) { + switch (result.state) { + case NoSupport: + warnNoSignatureSupport(); + break; + case Unsigned: + invokeSigningDialog(); + break; + case Signed: + invokeSignatureCheckingDialog(); + break; + } + } + }); + } + }; + + mPassClick.execute(); + return Hit.Widget; + } + + return Hit.Nothing; + } + + @TargetApi(11) + public boolean copySelection() { + final StringBuilder text = new StringBuilder(); + + processSelectedText(new TextProcessor() { + StringBuilder line; + + public void onStartLine() { + line = new StringBuilder(); + } + + public void onWord(TextWord word) { + if (line.length() > 0) + line.append(' '); + line.append(word.w); + } + + public void onEndLine() { + if (text.length() > 0) + text.append('\n'); + text.append(line); + } + }); + + if (text.length() == 0) + return false; + + int currentApiVersion = android.os.Build.VERSION.SDK_INT; + if (currentApiVersion >= android.os.Build.VERSION_CODES.HONEYCOMB) { + android.content.ClipboardManager cm = (android.content.ClipboardManager)mContext.getSystemService(Context.CLIPBOARD_SERVICE); + + cm.setPrimaryClip(ClipData.newPlainText("MuPDF", text)); + } else { + android.text.ClipboardManager cm = (android.text.ClipboardManager)mContext.getSystemService(Context.CLIPBOARD_SERVICE); + cm.setText(text); + } + + deselectText(); + + return true; + } + + public boolean markupSelection(final Annotation.Type type) { + final ArrayList<PointF> quadPoints = new ArrayList<PointF>(); + processSelectedText(new TextProcessor() { + RectF rect; + + public void onStartLine() { + rect = new RectF(); + } + + public void onWord(TextWord word) { + rect.union(word); + } + + public void onEndLine() { + if (!rect.isEmpty()) { + quadPoints.add(new PointF(rect.left, rect.bottom)); + quadPoints.add(new PointF(rect.right, rect.bottom)); + quadPoints.add(new PointF(rect.right, rect.top)); + quadPoints.add(new PointF(rect.left, rect.top)); + } + } + }); + + if (quadPoints.size() == 0) + return false; + + mAddStrikeOut = new AsyncTask<PointF[],Void,Void>() { + @Override + protected Void doInBackground(PointF[]... params) { + addMarkup(params[0], type); + return null; + } + + @Override + protected void onPostExecute(Void result) { + loadAnnotations(); + update(); + } + }; + + mAddStrikeOut.execute(quadPoints.toArray(new PointF[quadPoints.size()])); + + deselectText(); + + return true; + } + + public void deleteSelectedAnnotation() { + if (mSelectedAnnotationIndex != -1) { + if (mDeleteAnnotation != null) + mDeleteAnnotation.cancel(true); + + mDeleteAnnotation = new AsyncTask<Integer,Void,Void>() { + @Override + protected Void doInBackground(Integer... params) { + mCore.deleteAnnotation(mPageNumber, params[0]); + return null; + } + + @Override + protected void onPostExecute(Void result) { + loadAnnotations(); + update(); + } + }; + + mDeleteAnnotation.execute(mSelectedAnnotationIndex); + + mSelectedAnnotationIndex = -1; + setItemSelectBox(null); + } + } + + public void deselectAnnotation() { + mSelectedAnnotationIndex = -1; + setItemSelectBox(null); + } + + public boolean saveDraw() { + PointF[][] path = getDraw(); + + if (path == null) + return false; + + if (mAddInk != null) { + mAddInk.cancel(true); + mAddInk = null; + } + mAddInk = new AsyncTask<PointF[][],Void,Void>() { + @Override + protected Void doInBackground(PointF[][]... params) { + mCore.addInkAnnotation(mPageNumber, params[0]); + return null; + } + + @Override + protected void onPostExecute(Void result) { + loadAnnotations(); + update(); + } + + }; + + mAddInk.execute(getDraw()); + cancelDraw(); + + return true; + } + + + @Override + protected CancellableTaskDefinition<Void, Void> getDrawPageTask(final Bitmap bm, final int sizeX, final int sizeY, + final int patchX, final int patchY, final int patchWidth, final int patchHeight) { + return new MuPDFCancellableTaskDefinition<Void, Void>(mCore) { + @Override + public Void doInBackground(MuPDFCore.Cookie cookie, Void ... params) { + // Workaround bug in Android Honeycomb 3.x, where the bitmap generation count + // is not incremented when drawing. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && + Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) + bm.eraseColor(0); + mCore.drawPage(bm, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight, cookie); + return null; + } + }; + + } + + protected CancellableTaskDefinition<Void, Void> getUpdatePageTask(final Bitmap bm, final int sizeX, final int sizeY, + final int patchX, final int patchY, final int patchWidth, final int patchHeight) + { + return new MuPDFCancellableTaskDefinition<Void, Void>(mCore) { + + @Override + public Void doInBackground(MuPDFCore.Cookie cookie, Void ... params) { + // Workaround bug in Android Honeycomb 3.x, where the bitmap generation count + // is not incremented when drawing. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && + Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) + bm.eraseColor(0); + mCore.updatePage(bm, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight, cookie); + return null; + } + }; + } + + @Override + protected LinkInfo[] getLinkInfo() { + return mCore.getPageLinks(mPageNumber); + } + + @Override + protected TextWord[][] getText() { + return mCore.textLines(mPageNumber); + } + + @Override + protected void addMarkup(PointF[] quadPoints, Annotation.Type type) { + mCore.addMarkupAnnotation(mPageNumber, quadPoints, type); + } + + private void loadAnnotations() { + mAnnotations = null; + if (mLoadAnnotations != null) + mLoadAnnotations.cancel(true); + mLoadAnnotations = new AsyncTask<Void,Void,Annotation[]> () { + @Override + protected Annotation[] doInBackground(Void... params) { + return mCore.getAnnoations(mPageNumber); + } + + @Override + protected void onPostExecute(Annotation[] result) { + mAnnotations = result; + } + }; + + mLoadAnnotations.execute(); + } + + @Override + public void setPage(final int page, PointF size) { + loadAnnotations(); + + mLoadWidgetAreas = new AsyncTask<Void,Void,RectF[]> () { + @Override + protected RectF[] doInBackground(Void... arg0) { + return mCore.getWidgetAreas(page); + } + + @Override + protected void onPostExecute(RectF[] result) { + mWidgetAreas = result; + } + }; + + mLoadWidgetAreas.execute(); + + super.setPage(page, size); + } + + public void setScale(float scale) { + // This type of view scales automatically to fit the size + // determined by the parent view groups during layout + } + + @Override + public void releaseResources() { + if (mPassClick != null) { + mPassClick.cancel(true); + mPassClick = null; + } + + if (mLoadWidgetAreas != null) { + mLoadWidgetAreas.cancel(true); + mLoadWidgetAreas = null; + } + + if (mLoadAnnotations != null) { + mLoadAnnotations.cancel(true); + mLoadAnnotations = null; + } + + if (mSetWidgetText != null) { + mSetWidgetText.cancel(true); + mSetWidgetText = null; + } + + if (mSetWidgetChoice != null) { + mSetWidgetChoice.cancel(true); + mSetWidgetChoice = null; + } + + if (mAddStrikeOut != null) { + mAddStrikeOut.cancel(true); + mAddStrikeOut = null; + } + + if (mDeleteAnnotation != null) { + mDeleteAnnotation.cancel(true); + mDeleteAnnotation = null; + } + + super.releaseResources(); + } +} |