From 2fe3674a3f0aafb66a22b3c98fa4b8f90c42dfbe Mon Sep 17 00:00:00 2001 From: Michal Horn Date: Tue, 17 Feb 2015 12:03:51 +0100 Subject: [PATCH] Implement preview by the PdfView widget --- .../fel/dce/qrscanner/PreviewActivity.java | 87 +++-- .../dce/qrscanner/pdfviewer/PdfPageView.java | 310 ++++++++++++++++++ .../pdfviewer/PdfViewerException.java | 10 + .../main/res/layout-land/activity_preview.xml | 48 ++- .../src/main/res/layout/activity_preview.xml | 49 ++- .../mobile/src/main/res/values/strings.xml | 1 + 6 files changed, 468 insertions(+), 37 deletions(-) create mode 100644 QRScanner/mobile/src/main/java/cz/cvut/fel/dce/qrscanner/pdfviewer/PdfPageView.java create mode 100644 QRScanner/mobile/src/main/java/cz/cvut/fel/dce/qrscanner/pdfviewer/PdfViewerException.java diff --git a/QRScanner/mobile/src/main/java/cz/cvut/fel/dce/qrscanner/PreviewActivity.java b/QRScanner/mobile/src/main/java/cz/cvut/fel/dce/qrscanner/PreviewActivity.java index 2ea62c4..767d4bc 100644 --- a/QRScanner/mobile/src/main/java/cz/cvut/fel/dce/qrscanner/PreviewActivity.java +++ b/QRScanner/mobile/src/main/java/cz/cvut/fel/dce/qrscanner/PreviewActivity.java @@ -1,8 +1,11 @@ package cz.cvut.fel.dce.qrscanner; +import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; +import android.media.AudioManager; import android.net.Uri; +import android.os.AsyncTask; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.util.Log; @@ -12,10 +15,13 @@ import android.view.View; import android.view.ViewTreeObserver; import android.widget.Button; import android.widget.ImageView; +import android.widget.RelativeLayout; import android.widget.Toast; import cz.cvut.fel.dce.qrscanner.mupdf.MuPDFActivity; import cz.cvut.fel.dce.qrscanner.mupdf.MuPDFCore; +import cz.cvut.fel.dce.qrscanner.pdfviewer.PdfPageView; + import java.io.File; public class PreviewActivity extends ActionBarActivity implements ViewTreeObserver.OnGlobalLayoutListener { @@ -29,23 +35,32 @@ public class PreviewActivity extends ActionBarActivity implements ViewTreeObserv public static final String SKODA_COMP_MANUFACT_GUIDE = "Werkstatt_Einleitung.pdf"; private ImageView mPreviewImg; + private RelativeLayout mProgressContainer; private ViewTreeObserver mPreviewImgObserver; private String mComponentId; private String mComponentRootPath; + private PdfPageView mPdfView; + private boolean mPdfLoaded; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_preview); + mPdfLoaded = false; mPreviewImg = (ImageView) findViewById(R.id.imgComponent); - mPreviewImg.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + mProgressContainer = (RelativeLayout) findViewById(R.id.progress_container); + if (mProgressContainer == null) { + Log.e(TAG, "Progress container was not found."); + finish(); + } if (mPreviewImg != null) { + mPreviewImg.setScaleType(ImageView.ScaleType.CENTER_INSIDE); mPreviewImgObserver = mPreviewImg.getViewTreeObserver(); Log.i(TAG, "Registering mPreviewImgObserver OnGlobalLayoutListener."); mPreviewImgObserver.addOnGlobalLayoutListener(this); - } - else { + } else { Log.e(TAG, "ImageView for preview image could not be found in the resources."); mPreviewImgObserver = null; } @@ -62,8 +77,7 @@ public class PreviewActivity extends ActionBarActivity implements ViewTreeObserv toast.show(); finish(); } - } - else { + } else { Log.i(TAG, "No component id received"); finish(); } @@ -86,23 +100,19 @@ public class PreviewActivity extends ActionBarActivity implements ViewTreeObserv @Override public void onGlobalLayout() { + if (mPdfLoaded) { + Log.d(TAG, "PDF file already loaded."); + return; + } try { String picturePath = mComponentRootPath + SKODA_COMP_PICTURE_NAME; Log.i(TAG, "Path to component files: " + picturePath); + mPdfView = new PdfPageView(getApplicationContext(), picturePath); + mPdfView.setPage(0); + new LoadPageTask().execute(); + + mPdfLoaded = true; - MuPDFCore core = new MuPDFCore(getApplicationContext(), picturePath); - Log.d(TAG, "numpages: "+ core.countPages()); - MuPDFCore.Cookie cookie = core.new Cookie(); - int pageW = (int)core.getPageSize(0).x; - int pageH = (int)core.getPageSize(0).y; - Log.d(TAG, "page size: " + pageW + ", " + pageH); - Bitmap.Config conf = Bitmap.Config.ARGB_8888; - Bitmap previewBitmap = Bitmap.createBitmap(pageW, pageH, conf); - core.drawPage(previewBitmap, 0, pageW, pageH,0 , 0, pageW, pageH, cookie); - mPreviewImg.setMaxWidth(mPreviewImg.getWidth()); - mPreviewImg.setMaxHeight(mPreviewImg.getHeight()); - mPreviewImg.setImageBitmap(previewBitmap); - mPreviewImg.invalidate(); } catch (Exception e) { Toast toast = Toast.makeText(getApplicationContext(), "Component preview could not be loaded.", Toast.LENGTH_LONG); toast.show(); @@ -110,33 +120,64 @@ public class PreviewActivity extends ActionBarActivity implements ViewTreeObserv } } - /** Called when the user touches the button */ + /** + * Called when the user touches the button + */ public void showContacts(View view) { showPDF(mComponentRootPath + SKODA_COMP_CONTACTS); } - /** Called when the user touches the button */ + /** + * Called when the user touches the button + */ public void showManufacturing(View view) { showPDF(mComponentRootPath + SKODA_COMP_MANUFACTURING); } - /** Called when the user touches the button */ + /** + * Called when the user touches the button + */ public void showManufactImages(View view) { showPDF(mComponentRootPath + SKODA_COMP_MANUFACT_IMAGES); } - /** Called when the user touches the button */ + /** + * Called when the user touches the button + */ public void showManufactGuide(View view) { showPDF(mComponentRootPath + SKODA_COMP_MANUFACT_GUIDE); } private void showPDF(String filePath) { Uri uri = Uri.parse(filePath); - Intent pdfIntent = new Intent(this,MuPDFActivity.class); + Intent pdfIntent = new Intent(this, MuPDFActivity.class); pdfIntent.setAction(Intent.ACTION_VIEW); pdfIntent.setData(uri); startActivity(pdfIntent); } + private class LoadPageTask extends AsyncTask { + + @Override + protected Void doInBackground(Void[] objects) { + mPdfView.loadPage(); + return null; + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + Log.d(TAG, "Starting loading of the PDF page asynchronously."); + mProgressContainer.setVisibility(View.VISIBLE); + } + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + mProgressContainer.setVisibility(View.INVISIBLE); + mPreviewImg.setImageBitmap(mPdfView.getPageBitmap()); + mPreviewImg.invalidate(); + Log.d(TAG, "PDF page loaded."); + } + } } diff --git a/QRScanner/mobile/src/main/java/cz/cvut/fel/dce/qrscanner/pdfviewer/PdfPageView.java b/QRScanner/mobile/src/main/java/cz/cvut/fel/dce/qrscanner/pdfviewer/PdfPageView.java new file mode 100644 index 0000000..7e18f17 --- /dev/null +++ b/QRScanner/mobile/src/main/java/cz/cvut/fel/dce/qrscanner/pdfviewer/PdfPageView.java @@ -0,0 +1,310 @@ +package cz.cvut.fel.dce.qrscanner.pdfviewer; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.Log; +import android.view.View; + +import java.io.File; + +import cz.cvut.fel.dce.qrscanner.mupdf.MuPDFCore; + +/** + * Created by Michal Horn on 16.2.15. + */ +public class PdfPageView extends View { + /** + * Value with some allowed minimum and maximum. + * @author Michal Horn + * + * @param some comparable numeric data type + */ + class IntervalValue> { + /** + * Stored value + */ + private T mValue; + /** + * The lowest allowed value. All values stored in the object are + * higher or equal this minimum. + */ + private T mMinimum; + /** + * The highest allowed value. All values stored in the object are + * lower or equal this maximum. + */ + private T mMaximum; + + /** + * Class constructor. + * Creates object that represents a value in range . + * + * @param defValue Value to be stored in the object. Must be in the allowed range specified by other two parameters. + * @param minimum The lowes allowed value to be stored in the object. + * @param maximum The highest allowed value to be stored in the object. + * @throws Exception Throwed when minimal, maximal or inserted value were rejected. + */ + public IntervalValue(T defValue, T minimum, T maximum) throws Exception { + setMinimum(minimum); + setMaximum(maximum); + if (!setValue(defValue)) { + throw new Exception("Provided value is not in the range."); + } + } + + /** + * Store value in the object. The value must lie between the minimum and maximum + * defined for the object. + * @param value value to be stored in the object + * @return true - vale was stored successfully,
false - value could not be inserted because it was out of range. + */ + public boolean setValue(T value) { + if (value.compareTo(mMinimum) >= 0 && value.compareTo(mMaximum) <= 0) { + mValue = value; + return true; + } + else { + return false; + } + } + + /** + * Set minimum for values in the object. + * Last inserted value is not changed even if it is lower then the new minimum. + * @param min new allowed minimum for the values stored to the object. + * @throws Exception throwed when new minimum is higher than old maximum. + */ + public void setMinimum(T min) throws Exception { + if (mMaximum != null && mMinimum != null && mMinimum.compareTo(mMaximum) > 0) { + throw new Exception("Minimum is higher then maximum."); + } + mMinimum = min; + } + + /** + * Set maximum for values in the object. + * Last inserted value is not changed even if it is higher then the new maximum. + * @param max new allowed maximum for the values stored to the object. + * @throws Exception throwed when new maximum is lower than old minimum. + */ + public void setMaximum(T max) throws Exception { + if (mMinimum != null && mMaximum != null && mMaximum.compareTo(mMinimum) < 0) { + throw new Exception("Maximum is lower then minimum."); + } + mMaximum = max; + } + + /** + * Get value stored in the object + * @return value stored in the object. + */ + public T getValue() { + return mValue; + } + + /** + * Get the lowest allowed value that can be stored in the object. + * @return minimal value + */ + public T getMinimum() { + return mMinimum; + } + + /** + * Get the highest allowed value that can be stored in the object. + * @return maximal value + */ + public T getMaximum() { + return mMaximum; + } + } + + /** + * Interface for events generated by the view + * @author Michal Horn + * + */ + public interface SceneChange { + /** + * Invoked every time when the scene should be redrawed. + * @param source + */ + public void onViewChanged(PdfPageView source); + + /** + * Invoked once the PDF page is loaded and prepared as a bitmap to be shown + * @param source + */ + public void onPageLoaded(PdfPageView source); + } + + public static final String TAG = "PdfPageView"; + protected Paint mPaint; + protected IntervalValue mZoom; + protected IntervalValue mXPosition; + protected IntervalValue mYPosition; + protected SceneChange mListener; + protected float mSceneHeight; + protected float mSceneWidth; + protected Bitmap mPdfBitmap; + protected File mPdfFile; + protected Boolean mPageLoaded; + private MuPDFCore mPdfCore; + private IntervalValue mPage; + private MuPDFCore.Cookie mPdfCookie; + + public PdfPageView(Context context, String filePath) throws Exception { + super(context); + mPageLoaded = false; + mPdfFile = new File(filePath); + if (!mPdfFile.exists()) { + throw new PdfViewerException("File " + filePath + " does not exist."); + } + mPaint = new Paint(); + mPdfCore = new MuPDFCore(context, mPdfFile.getAbsolutePath()); + mPdfCookie = mPdfCore.new Cookie(); + if (mPdfCore == null) { + throw new PdfViewerException("The PDF file could not be loaded."); + } + mPage = new IntervalValue<>(0, 0, mPdfCore.countPages()-1); + Log.i(TAG, "Pages: " + mPage.toString()); + } + + public int getActualPage() { + return mPage.getValue(); + } + + public int getLastPage() { + return mPage.getMaximum(); + } + + public boolean setPage(int page) { + return mPage.setValue(page); + } + + public void loadPage() { + long startTime = System.currentTimeMillis(); + try { + Log.i(TAG, "Loading PDF page " + getActualPage()); + mSceneWidth = mPdfCore.getPageSize(getActualPage()).x; + mSceneHeight = mPdfCore.getPageSize(getActualPage()).y; + Log.i(TAG, "Page size: " + mSceneWidth + "x" + mSceneHeight); + Bitmap.Config mPdfBitmapConf = Bitmap.Config.ARGB_8888; + mPdfBitmap = Bitmap.createBitmap((int)mSceneWidth, (int)mSceneHeight, mPdfBitmapConf); + mPdfCore.drawPage(mPdfBitmap, 0, (int) mSceneWidth, (int) mSceneHeight, 0, 0, (int) mSceneWidth, (int) mSceneHeight, mPdfCookie); + + mXPosition = new IntervalValue<>(640.0f/2, -mSceneWidth+640.0f/2, mSceneWidth+640.0f/2); + mYPosition = new IntervalValue<>(360.0f/2, -mSceneHeight+360.0f/2, mSceneHeight+360.0f/2); + mZoom = new IntervalValue<>(1.0f, 0.2f, 2.0f); + } catch (Exception e) { + Log.e(TAG, "Error in setting page dimensions."); + e.printStackTrace(); + } + long endTime = System.currentTimeMillis(); + mPageLoaded = true; + Log.i(TAG, "PDF page loaded in " + Long.toString(endTime - startTime) + "ms."); + if (mListener != null) { + mListener.onPageLoaded(this); + } + } + + public Bitmap getPageBitmap() { + return mPdfBitmap; + } + + + + /** + * Set a listener of the events + * @param listener listener of the events + */ + public void setListener(SceneChange listener) { + mListener = listener; + } + + public void clearListener() { + mListener = null; + } + + /** + * Zoom scene function. The scene can be zoomed in or out only in limits + * defined in constructor. + * @param deltaRatio when > 0 - zoom in,
when < 0 - zoom out + */ + public void zoom(float deltaRatio) { + if (!mPageLoaded) return; + float ratio = mZoom.getValue(); + ratio += deltaRatio; + if (mZoom.setValue(ratio) == true && mListener != null) { + mListener.onViewChanged(this); + } + } + + /** + * Move scene function. The scene can be moved only in the rectangle specified + * by limits defined in constructor. + * @param x change of the x coordinate relatively to the actual position + * @param y change of the y coordinate relatively to the actual position + */ + public void move(float x, float y) { + if (!mPageLoaded) return; + boolean xWasSet = mXPosition.setValue(mXPosition.getValue() + x/mZoom.getValue()*2.2f); + boolean yWasSet = mYPosition.setValue(mYPosition.getValue() + y/mZoom.getValue()*2.2f); + + if ((xWasSet || yWasSet) && mListener != null) { + mListener.onViewChanged(this); + } + } + /** + * @return actual scene zoom ratio + */ + public float getZoomRatio() { + return mZoom.getValue(); + } + + /** + * @return actual scene position on X axis + */ + public float getXPosition() { + return mXPosition.getValue(); + } + + /** + * @return actual scene positoin on Y axis + */ + public float getYPosition() { + return mYPosition.getValue(); + } + + /** + * @return how tall the scene is + */ + public float getSceneHeight() { + return mSceneHeight; + } + + /** + * @return how wide the scene is + */ + public float getSceneWidth() { + return mSceneWidth; + } + + @Override + protected void onDraw(Canvas c) { + if (!mPageLoaded) return; + draw(c); + super.onDraw(c); + } + + @Override + public void draw(Canvas c) { + if (!mPageLoaded) return; + c.translate(c.getWidth() / 2, c.getHeight() / 2); + c.scale(mZoom.getValue(), mZoom.getValue()); + c.translate(-mXPosition.getValue(), -mYPosition.getValue()); + c.drawBitmap(mPdfBitmap, 0, 0, mPaint); + } +} diff --git a/QRScanner/mobile/src/main/java/cz/cvut/fel/dce/qrscanner/pdfviewer/PdfViewerException.java b/QRScanner/mobile/src/main/java/cz/cvut/fel/dce/qrscanner/pdfviewer/PdfViewerException.java new file mode 100644 index 0000000..36cf105 --- /dev/null +++ b/QRScanner/mobile/src/main/java/cz/cvut/fel/dce/qrscanner/pdfviewer/PdfViewerException.java @@ -0,0 +1,10 @@ +package cz.cvut.fel.dce.qrscanner.pdfviewer; + +/** + * Created by michal on 16.2.15. + */ +public class PdfViewerException extends Exception { + PdfViewerException(String s){ + super(s); + } +} diff --git a/QRScanner/mobile/src/main/res/layout-land/activity_preview.xml b/QRScanner/mobile/src/main/res/layout-land/activity_preview.xml index b3f52fe..818eb65 100644 --- a/QRScanner/mobile/src/main/res/layout-land/activity_preview.xml +++ b/QRScanner/mobile/src/main/res/layout-land/activity_preview.xml @@ -114,12 +114,46 @@ - + + + + + + + + + + + \ No newline at end of file diff --git a/QRScanner/mobile/src/main/res/layout/activity_preview.xml b/QRScanner/mobile/src/main/res/layout/activity_preview.xml index 7573b14..f2afe02 100644 --- a/QRScanner/mobile/src/main/res/layout/activity_preview.xml +++ b/QRScanner/mobile/src/main/res/layout/activity_preview.xml @@ -49,12 +49,47 @@ android:onClick="showContacts" /> - + + + + + + + + + + + + diff --git a/QRScanner/mobile/src/main/res/values/strings.xml b/QRScanner/mobile/src/main/res/values/strings.xml index 99c1eb1..1d7a6c0 100644 --- a/QRScanner/mobile/src/main/res/values/strings.xml +++ b/QRScanner/mobile/src/main/res/values/strings.xml @@ -65,6 +65,7 @@ Pictured manufacturing process Workshop manual Select document to view + Loading... -- 2.39.2