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 null;
}
@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.getAnnotations(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();
}
}