From 5822ff1619fda7f0cc0b10f77d351eb459e3c67c Mon Sep 17 00:00:00 2001 From: Robin Watts Date: Mon, 21 Jan 2013 12:54:06 +0000 Subject: Rename app. Due to a clash on Google Play, we need to rename the apps main class from com.artifex.mupdf to something else. We choose com.artifex.mupdfdemo. Any user of the code in their own app should rename it similarly. To simplify this process we add some macros in the C. Various renames and lots of tedious package name editing is still required in the Java though. --- android/src/com/artifex/mupdf/AsyncTask.java | 671 ------------- android/src/com/artifex/mupdf/BitmapHolder.java | 21 - .../src/com/artifex/mupdf/ChoosePDFActivity.java | 179 ---- .../src/com/artifex/mupdf/ChoosePDFAdapter.java | 64 -- android/src/com/artifex/mupdf/ChoosePDFItem.java | 15 - android/src/com/artifex/mupdf/LinkInfo.java | 14 - .../src/com/artifex/mupdf/LinkInfoExternal.java | 14 - .../src/com/artifex/mupdf/LinkInfoInternal.java | 14 - android/src/com/artifex/mupdf/LinkInfoRemote.java | 18 - android/src/com/artifex/mupdf/LinkInfoVisitor.java | 7 - android/src/com/artifex/mupdf/MuPDFActivity.java | 1031 -------------------- android/src/com/artifex/mupdf/MuPDFAlert.java | 21 - .../src/com/artifex/mupdf/MuPDFAlertInternal.java | 30 - android/src/com/artifex/mupdf/MuPDFCore.java | 251 ----- .../src/com/artifex/mupdf/MuPDFPageAdapter.java | 72 -- android/src/com/artifex/mupdf/MuPDFPageView.java | 243 ----- android/src/com/artifex/mupdf/OutlineActivity.java | 30 - .../src/com/artifex/mupdf/OutlineActivityData.java | 17 - android/src/com/artifex/mupdf/OutlineAdapter.java | 46 - android/src/com/artifex/mupdf/OutlineItem.java | 14 - android/src/com/artifex/mupdf/PageView.java | 640 ------------ android/src/com/artifex/mupdf/ReaderView.java | 583 ----------- android/src/com/artifex/mupdf/TextChar.java | 12 - android/src/com/artifex/mupdf/TextWord.java | 17 - android/src/com/artifex/mupdf/WidgetType.java | 8 - android/src/com/artifex/mupdfdemo/AsyncTask.java | 671 +++++++++++++ .../src/com/artifex/mupdfdemo/BitmapHolder.java | 21 + .../com/artifex/mupdfdemo/ChoosePDFActivity.java | 179 ++++ .../com/artifex/mupdfdemo/ChoosePDFAdapter.java | 64 ++ .../src/com/artifex/mupdfdemo/ChoosePDFItem.java | 15 + android/src/com/artifex/mupdfdemo/LinkInfo.java | 14 + .../com/artifex/mupdfdemo/LinkInfoExternal.java | 14 + .../com/artifex/mupdfdemo/LinkInfoInternal.java | 14 + .../src/com/artifex/mupdfdemo/LinkInfoRemote.java | 18 + .../src/com/artifex/mupdfdemo/LinkInfoVisitor.java | 7 + .../src/com/artifex/mupdfdemo/MuPDFActivity.java | 1031 ++++++++++++++++++++ android/src/com/artifex/mupdfdemo/MuPDFAlert.java | 21 + .../com/artifex/mupdfdemo/MuPDFAlertInternal.java | 30 + android/src/com/artifex/mupdfdemo/MuPDFCore.java | 251 +++++ .../com/artifex/mupdfdemo/MuPDFPageAdapter.java | 72 ++ .../src/com/artifex/mupdfdemo/MuPDFPageView.java | 243 +++++ .../src/com/artifex/mupdfdemo/OutlineActivity.java | 30 + .../com/artifex/mupdfdemo/OutlineActivityData.java | 17 + .../src/com/artifex/mupdfdemo/OutlineAdapter.java | 46 + android/src/com/artifex/mupdfdemo/OutlineItem.java | 14 + android/src/com/artifex/mupdfdemo/PageView.java | 640 ++++++++++++ android/src/com/artifex/mupdfdemo/ReaderView.java | 583 +++++++++++ android/src/com/artifex/mupdfdemo/TextChar.java | 12 + android/src/com/artifex/mupdfdemo/TextWord.java | 17 + android/src/com/artifex/mupdfdemo/WidgetType.java | 8 + 50 files changed, 4032 insertions(+), 4032 deletions(-) delete mode 100644 android/src/com/artifex/mupdf/AsyncTask.java delete mode 100644 android/src/com/artifex/mupdf/BitmapHolder.java delete mode 100644 android/src/com/artifex/mupdf/ChoosePDFActivity.java delete mode 100644 android/src/com/artifex/mupdf/ChoosePDFAdapter.java delete mode 100644 android/src/com/artifex/mupdf/ChoosePDFItem.java delete mode 100644 android/src/com/artifex/mupdf/LinkInfo.java delete mode 100644 android/src/com/artifex/mupdf/LinkInfoExternal.java delete mode 100644 android/src/com/artifex/mupdf/LinkInfoInternal.java delete mode 100644 android/src/com/artifex/mupdf/LinkInfoRemote.java delete mode 100644 android/src/com/artifex/mupdf/LinkInfoVisitor.java delete mode 100644 android/src/com/artifex/mupdf/MuPDFActivity.java delete mode 100644 android/src/com/artifex/mupdf/MuPDFAlert.java delete mode 100644 android/src/com/artifex/mupdf/MuPDFAlertInternal.java delete mode 100644 android/src/com/artifex/mupdf/MuPDFCore.java delete mode 100644 android/src/com/artifex/mupdf/MuPDFPageAdapter.java delete mode 100644 android/src/com/artifex/mupdf/MuPDFPageView.java delete mode 100644 android/src/com/artifex/mupdf/OutlineActivity.java delete mode 100644 android/src/com/artifex/mupdf/OutlineActivityData.java delete mode 100644 android/src/com/artifex/mupdf/OutlineAdapter.java delete mode 100644 android/src/com/artifex/mupdf/OutlineItem.java delete mode 100644 android/src/com/artifex/mupdf/PageView.java delete mode 100644 android/src/com/artifex/mupdf/ReaderView.java delete mode 100644 android/src/com/artifex/mupdf/TextChar.java delete mode 100644 android/src/com/artifex/mupdf/TextWord.java delete mode 100644 android/src/com/artifex/mupdf/WidgetType.java create mode 100644 android/src/com/artifex/mupdfdemo/AsyncTask.java create mode 100644 android/src/com/artifex/mupdfdemo/BitmapHolder.java create mode 100644 android/src/com/artifex/mupdfdemo/ChoosePDFActivity.java create mode 100644 android/src/com/artifex/mupdfdemo/ChoosePDFAdapter.java create mode 100644 android/src/com/artifex/mupdfdemo/ChoosePDFItem.java create mode 100644 android/src/com/artifex/mupdfdemo/LinkInfo.java create mode 100644 android/src/com/artifex/mupdfdemo/LinkInfoExternal.java create mode 100644 android/src/com/artifex/mupdfdemo/LinkInfoInternal.java create mode 100644 android/src/com/artifex/mupdfdemo/LinkInfoRemote.java create mode 100644 android/src/com/artifex/mupdfdemo/LinkInfoVisitor.java create mode 100644 android/src/com/artifex/mupdfdemo/MuPDFActivity.java create mode 100644 android/src/com/artifex/mupdfdemo/MuPDFAlert.java create mode 100644 android/src/com/artifex/mupdfdemo/MuPDFAlertInternal.java create mode 100644 android/src/com/artifex/mupdfdemo/MuPDFCore.java create mode 100644 android/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java create mode 100644 android/src/com/artifex/mupdfdemo/MuPDFPageView.java create mode 100644 android/src/com/artifex/mupdfdemo/OutlineActivity.java create mode 100644 android/src/com/artifex/mupdfdemo/OutlineActivityData.java create mode 100644 android/src/com/artifex/mupdfdemo/OutlineAdapter.java create mode 100644 android/src/com/artifex/mupdfdemo/OutlineItem.java create mode 100644 android/src/com/artifex/mupdfdemo/PageView.java create mode 100644 android/src/com/artifex/mupdfdemo/ReaderView.java create mode 100644 android/src/com/artifex/mupdfdemo/TextChar.java create mode 100644 android/src/com/artifex/mupdfdemo/TextWord.java create mode 100644 android/src/com/artifex/mupdfdemo/WidgetType.java (limited to 'android/src') diff --git a/android/src/com/artifex/mupdf/AsyncTask.java b/android/src/com/artifex/mupdf/AsyncTask.java deleted file mode 100644 index 424adc1b..00000000 --- a/android/src/com/artifex/mupdf/AsyncTask.java +++ /dev/null @@ -1,671 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.artifex.mupdf; - -import java.util.ArrayDeque; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.FutureTask; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import android.os.Process; -import android.os.Handler; -import android.os.Message; - -/** - *

AsyncTask enables proper and easy use of the UI thread. This class allows to - * perform background operations and publish results on the UI thread without - * having to manipulate threads and/or handlers.

- * - *

AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler} - * and does not constitute a generic threading framework. AsyncTasks should ideally be - * used for short operations (a few seconds at the most.) If you need to keep threads - * running for long periods of time, it is highly recommended you use the various APIs - * provided by the java.util.concurrent pacakge such as {@link Executor}, - * {@link ThreadPoolExecutor} and {@link FutureTask}.

- * - *

An asynchronous task is defined by a computation that runs on a background thread and - * whose result is published on the UI thread. An asynchronous task is defined by 3 generic - * types, called Params, Progress and Result, - * and 4 steps, called onPreExecute, doInBackground, - * onProgressUpdate and onPostExecute.

- * - *
- *

Developer Guides

- *

For more information about using tasks and threads, read the - * Processes and - * Threads developer guide.

- *
- * - *

Usage

- *

AsyncTask must be subclassed to be used. The subclass will override at least - * one method ({@link #doInBackground}), and most often will override a - * second one ({@link #onPostExecute}.)

- * - *

Here is an example of subclassing:

- *
- * private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
- *     protected Long doInBackground(URL... urls) {
- *         int count = urls.length;
- *         long totalSize = 0;
- *         for (int i = 0; i < count; i++) {
- *             totalSize += Downloader.downloadFile(urls[i]);
- *             publishProgress((int) ((i / (float) count) * 100));
- *             // Escape early if cancel() is called
- *             if (isCancelled()) break;
- *         }
- *         return totalSize;
- *     }
- *
- *     protected void onProgressUpdate(Integer... progress) {
- *         setProgressPercent(progress[0]);
- *     }
- *
- *     protected void onPostExecute(Long result) {
- *         showDialog("Downloaded " + result + " bytes");
- *     }
- * }
- * 
- * - *

Once created, a task is executed very simply:

- *
- * new DownloadFilesTask().execute(url1, url2, url3);
- * 
- * - *

AsyncTask's generic types

- *

The three types used by an asynchronous task are the following:

- *
    - *
  1. Params, the type of the parameters sent to the task upon - * execution.
  2. - *
  3. Progress, the type of the progress units published during - * the background computation.
  4. - *
  5. Result, the type of the result of the background - * computation.
  6. - *
- *

Not all types are always used by an asynchronous task. To mark a type as unused, - * simply use the type {@link Void}:

- *
- * private class MyTask extends AsyncTask<Void, Void, Void> { ... }
- * 
- * - *

The 4 steps

- *

When an asynchronous task is executed, the task goes through 4 steps:

- *
    - *
  1. {@link #onPreExecute()}, invoked on the UI thread before the task - * is executed. This step is normally used to setup the task, for instance by - * showing a progress bar in the user interface.
  2. - *
  3. {@link #doInBackground}, invoked on the background thread - * immediately after {@link #onPreExecute()} finishes executing. This step is used - * to perform background computation that can take a long time. The parameters - * of the asynchronous task are passed to this step. The result of the computation must - * be returned by this step and will be passed back to the last step. This step - * can also use {@link #publishProgress} to publish one or more units - * of progress. These values are published on the UI thread, in the - * {@link #onProgressUpdate} step.
  4. - *
  5. {@link #onProgressUpdate}, invoked on the UI thread after a - * call to {@link #publishProgress}. The timing of the execution is - * undefined. This method is used to display any form of progress in the user - * interface while the background computation is still executing. For instance, - * it can be used to animate a progress bar or show logs in a text field.
  6. - *
  7. {@link #onPostExecute}, invoked on the UI thread after the background - * computation finishes. The result of the background computation is passed to - * this step as a parameter.
  8. - *
- * - *

Cancelling a task

- *

A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking - * this method will cause subsequent calls to {@link #isCancelled()} to return true. - * After invoking this method, {@link #onCancelled(Object)}, instead of - * {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])} - * returns. To ensure that a task is cancelled as quickly as possible, you should always - * check the return value of {@link #isCancelled()} periodically from - * {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)

- * - *

Threading rules

- *

There are a few threading rules that must be followed for this class to - * work properly:

- * - * - *

Memory observability

- *

AsyncTask guarantees that all callback calls are synchronized in such a way that the following - * operations are safe without explicit synchronizations.

- * - * - *

Order of execution

- *

When first introduced, AsyncTasks were executed serially on a single background - * thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed - * to a pool of threads allowing multiple tasks to operate in parallel. Starting with - * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single - * thread to avoid common application errors caused by parallel execution.

- *

If you truly want parallel execution, you can invoke - * {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with - * {@link #THREAD_POOL_EXECUTOR}.

- */ -public abstract class AsyncTask { - private static final String LOG_TAG = "AsyncTask"; - - private static final int CORE_POOL_SIZE = 5; - private static final int MAXIMUM_POOL_SIZE = 128; - private static final int KEEP_ALIVE = 1; - - private static final ThreadFactory sThreadFactory = new ThreadFactory() { - private final AtomicInteger mCount = new AtomicInteger(1); - - public Thread newThread(Runnable r) { - return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); - } - }; - - private static final BlockingQueue sPoolWorkQueue = - new LinkedBlockingQueue(10); - - /** - * An {@link Executor} that can be used to execute tasks in parallel. - */ - public static final Executor THREAD_POOL_EXECUTOR - = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, - TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); - - /** - * An {@link Executor} that executes tasks one at a time in serial - * order. This serialization is global to a particular process. - */ - public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); - - private static final int MESSAGE_POST_RESULT = 0x1; - private static final int MESSAGE_POST_PROGRESS = 0x2; - - private static final InternalHandler sHandler = new InternalHandler(); - - private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; - private final WorkerRunnable mWorker; - private final FutureTask mFuture; - - private volatile Status mStatus = Status.PENDING; - - private final AtomicBoolean mCancelled = new AtomicBoolean(); - private final AtomicBoolean mTaskInvoked = new AtomicBoolean(); - - private static class SerialExecutor implements Executor { - final ArrayDeque mTasks = new ArrayDeque(); - Runnable mActive; - - public synchronized void execute(final Runnable r) { - mTasks.offer(new Runnable() { - public void run() { - try { - r.run(); - } finally { - scheduleNext(); - } - } - }); - if (mActive == null) { - scheduleNext(); - } - } - - protected synchronized void scheduleNext() { - if ((mActive = mTasks.poll()) != null) { - THREAD_POOL_EXECUTOR.execute(mActive); - } - } - } - - /** - * Indicates the current status of the task. Each status will be set only once - * during the lifetime of a task. - */ - public enum Status { - /** - * Indicates that the task has not been executed yet. - */ - PENDING, - /** - * Indicates that the task is running. - */ - RUNNING, - /** - * Indicates that {@link AsyncTask#onPostExecute} has finished. - */ - FINISHED, - } - - /** @hide Used to force static handler to be created. */ - public static void init() { - sHandler.getLooper(); - } - - /** @hide */ - public static void setDefaultExecutor(Executor exec) { - sDefaultExecutor = exec; - } - - /** - * Creates a new asynchronous task. This constructor must be invoked on the UI thread. - */ - public AsyncTask() { - mWorker = new WorkerRunnable() { - public Result call() throws Exception { - mTaskInvoked.set(true); - - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - //noinspection unchecked - return postResult(doInBackground(mParams)); - } - }; - - mFuture = new FutureTask(mWorker) { - @Override - protected void done() { - try { - postResultIfNotInvoked(get()); - } catch (InterruptedException e) { - android.util.Log.w(LOG_TAG, e); - } catch (ExecutionException e) { - throw new RuntimeException("An error occured while executing doInBackground()", - e.getCause()); - } catch (CancellationException e) { - postResultIfNotInvoked(null); - } - } - }; - } - - private void postResultIfNotInvoked(Result result) { - final boolean wasTaskInvoked = mTaskInvoked.get(); - if (!wasTaskInvoked) { - postResult(result); - } - } - - private Result postResult(Result result) { - @SuppressWarnings("unchecked") - Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, - new AsyncTaskResult(this, result)); - message.sendToTarget(); - return result; - } - - /** - * Returns the current status of this task. - * - * @return The current status. - */ - public final Status getStatus() { - return mStatus; - } - - /** - * Override this method to perform a computation on a background thread. The - * specified parameters are the parameters passed to {@link #execute} - * by the caller of this task. - * - * This method can call {@link #publishProgress} to publish updates - * on the UI thread. - * - * @param params The parameters of the task. - * - * @return A result, defined by the subclass of this task. - * - * @see #onPreExecute() - * @see #onPostExecute - * @see #publishProgress - */ - protected abstract Result doInBackground(Params... params); - - /** - * Runs on the UI thread before {@link #doInBackground}. - * - * @see #onPostExecute - * @see #doInBackground - */ - protected void onPreExecute() { - } - - /** - *

Runs on the UI thread after {@link #doInBackground}. The - * specified result is the value returned by {@link #doInBackground}.

- * - *

This method won't be invoked if the task was cancelled.

- * - * @param result The result of the operation computed by {@link #doInBackground}. - * - * @see #onPreExecute - * @see #doInBackground - * @see #onCancelled(Object) - */ - @SuppressWarnings({"UnusedDeclaration"}) - protected void onPostExecute(Result result) { - } - - /** - * Runs on the UI thread after {@link #publishProgress} is invoked. - * The specified values are the values passed to {@link #publishProgress}. - * - * @param values The values indicating progress. - * - * @see #publishProgress - * @see #doInBackground - */ - @SuppressWarnings({"UnusedDeclaration"}) - protected void onProgressUpdate(Progress... values) { - } - - /** - *

Runs on the UI thread after {@link #cancel(boolean)} is invoked and - * {@link #doInBackground(Object[])} has finished.

- * - *

The default implementation simply invokes {@link #onCancelled()} and - * ignores the result. If you write your own implementation, do not call - * super.onCancelled(result).

- * - * @param result The result, if any, computed in - * {@link #doInBackground(Object[])}, can be null - * - * @see #cancel(boolean) - * @see #isCancelled() - */ - @SuppressWarnings({"UnusedParameters"}) - protected void onCancelled(Result result) { - onCancelled(); - } - - /** - *

Applications should preferably override {@link #onCancelled(Object)}. - * This method is invoked by the default implementation of - * {@link #onCancelled(Object)}.

- * - *

Runs on the UI thread after {@link #cancel(boolean)} is invoked and - * {@link #doInBackground(Object[])} has finished.

- * - * @see #onCancelled(Object) - * @see #cancel(boolean) - * @see #isCancelled() - */ - protected void onCancelled() { - } - - /** - * Returns true if this task was cancelled before it completed - * normally. If you are calling {@link #cancel(boolean)} on the task, - * the value returned by this method should be checked periodically from - * {@link #doInBackground(Object[])} to end the task as soon as possible. - * - * @return true if task was cancelled before it completed - * - * @see #cancel(boolean) - */ - public final boolean isCancelled() { - return mCancelled.get(); - } - - /** - *

Attempts to cancel execution of this task. This attempt will - * fail if the task has already completed, already been cancelled, - * or could not be cancelled for some other reason. If successful, - * and this task has not started when cancel is called, - * this task should never run. If the task has already started, - * then the mayInterruptIfRunning parameter determines - * whether the thread executing this task should be interrupted in - * an attempt to stop the task.

- * - *

Calling this method will result in {@link #onCancelled(Object)} being - * invoked on the UI thread after {@link #doInBackground(Object[])} - * returns. Calling this method guarantees that {@link #onPostExecute(Object)} - * is never invoked. After invoking this method, you should check the - * value returned by {@link #isCancelled()} periodically from - * {@link #doInBackground(Object[])} to finish the task as early as - * possible.

- * - * @param mayInterruptIfRunning true if the thread executing this - * task should be interrupted; otherwise, in-progress tasks are allowed - * to complete. - * - * @return false if the task could not be cancelled, - * typically because it has already completed normally; - * true otherwise - * - * @see #isCancelled() - * @see #onCancelled(Object) - */ - public final boolean cancel(boolean mayInterruptIfRunning) { - mCancelled.set(true); - return mFuture.cancel(mayInterruptIfRunning); - } - - /** - * Waits if necessary for the computation to complete, and then - * retrieves its result. - * - * @return The computed result. - * - * @throws CancellationException If the computation was cancelled. - * @throws ExecutionException If the computation threw an exception. - * @throws InterruptedException If the current thread was interrupted - * while waiting. - */ - public final Result get() throws InterruptedException, ExecutionException { - return mFuture.get(); - } - - /** - * Waits if necessary for at most the given time for the computation - * to complete, and then retrieves its result. - * - * @param timeout Time to wait before cancelling the operation. - * @param unit The time unit for the timeout. - * - * @return The computed result. - * - * @throws CancellationException If the computation was cancelled. - * @throws ExecutionException If the computation threw an exception. - * @throws InterruptedException If the current thread was interrupted - * while waiting. - * @throws TimeoutException If the wait timed out. - */ - public final Result get(long timeout, TimeUnit unit) throws InterruptedException, - ExecutionException, TimeoutException { - return mFuture.get(timeout, unit); - } - - /** - * Executes the task with the specified parameters. The task returns - * itself (this) so that the caller can keep a reference to it. - * - *

Note: this function schedules the task on a queue for a single background - * thread or pool of threads depending on the platform version. When first - * introduced, AsyncTasks were executed serially on a single background thread. - * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed - * to a pool of threads allowing multiple tasks to operate in parallel. Starting - * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being - * executed on a single thread to avoid common application errors caused - * by parallel execution. If you truly want parallel execution, you can use - * the {@link #executeOnExecutor} version of this method - * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings - * on its use. - * - *

This method must be invoked on the UI thread. - * - * @param params The parameters of the task. - * - * @return This instance of AsyncTask. - * - * @throws IllegalStateException If {@link #getStatus()} returns either - * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. - * - * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) - * @see #execute(Runnable) - */ - public final AsyncTask execute(Params... params) { - return executeOnExecutor(sDefaultExecutor, params); - } - - /** - * Executes the task with the specified parameters. The task returns - * itself (this) so that the caller can keep a reference to it. - * - *

This method is typically used with {@link #THREAD_POOL_EXECUTOR} to - * allow multiple tasks to run in parallel on a pool of threads managed by - * AsyncTask, however you can also use your own {@link Executor} for custom - * behavior. - * - *

Warning: Allowing multiple tasks to run in parallel from - * a thread pool is generally not what one wants, because the order - * of their operation is not defined. For example, if these tasks are used - * to modify any state in common (such as writing a file due to a button click), - * there are no guarantees on the order of the modifications. - * Without careful work it is possible in rare cases for the newer version - * of the data to be over-written by an older one, leading to obscure data - * loss and stability issues. Such changes are best - * executed in serial; to guarantee such work is serialized regardless of - * platform version you can use this function with {@link #SERIAL_EXECUTOR}. - * - *

This method must be invoked on the UI thread. - * - * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a - * convenient process-wide thread pool for tasks that are loosely coupled. - * @param params The parameters of the task. - * - * @return This instance of AsyncTask. - * - * @throws IllegalStateException If {@link #getStatus()} returns either - * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. - * - * @see #execute(Object[]) - */ - public final AsyncTask executeOnExecutor(Executor exec, - Params... params) { - if (mStatus != Status.PENDING) { - switch (mStatus) { - case RUNNING: - throw new IllegalStateException("Cannot execute task:" - + " the task is already running."); - case FINISHED: - throw new IllegalStateException("Cannot execute task:" - + " the task has already been executed " - + "(a task can be executed only once)"); - } - } - - mStatus = Status.RUNNING; - - onPreExecute(); - - mWorker.mParams = params; - exec.execute(mFuture); - - return this; - } - - /** - * Convenience version of {@link #execute(Object...)} for use with - * a simple Runnable object. See {@link #execute(Object[])} for more - * information on the order of execution. - * - * @see #execute(Object[]) - * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) - */ - public static void execute(Runnable runnable) { - sDefaultExecutor.execute(runnable); - } - - /** - * This method can be invoked from {@link #doInBackground} to - * publish updates on the UI thread while the background computation is - * still running. Each call to this method will trigger the execution of - * {@link #onProgressUpdate} on the UI thread. - * - * {@link #onProgressUpdate} will note be called if the task has been - * canceled. - * - * @param values The progress values to update the UI with. - * - * @see #onProgressUpdate - * @see #doInBackground - */ - protected final void publishProgress(Progress... values) { - if (!isCancelled()) { - sHandler.obtainMessage(MESSAGE_POST_PROGRESS, - new AsyncTaskResult(this, values)).sendToTarget(); - } - } - - private void finish(Result result) { - if (isCancelled()) { - onCancelled(result); - } else { - onPostExecute(result); - } - mStatus = Status.FINISHED; - } - - private static class InternalHandler extends Handler { - @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) - @Override - public void handleMessage(Message msg) { - AsyncTaskResult result = (AsyncTaskResult) msg.obj; - switch (msg.what) { - case MESSAGE_POST_RESULT: - // There is only one result - result.mTask.finish(result.mData[0]); - break; - case MESSAGE_POST_PROGRESS: - result.mTask.onProgressUpdate(result.mData); - break; - } - } - } - - private static abstract class WorkerRunnable implements Callable { - Params[] mParams; - } - - @SuppressWarnings({"RawUseOfParameterizedType"}) - private static class AsyncTaskResult { - final AsyncTask mTask; - final Data[] mData; - - AsyncTaskResult(AsyncTask task, Data... data) { - mTask = task; - mData = data; - } - } -} diff --git a/android/src/com/artifex/mupdf/BitmapHolder.java b/android/src/com/artifex/mupdf/BitmapHolder.java deleted file mode 100644 index 4e1edf63..00000000 --- a/android/src/com/artifex/mupdf/BitmapHolder.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.artifex.mupdf; - -import android.graphics.Bitmap; - -public class BitmapHolder { - private Bitmap bm; - - public BitmapHolder() { - bm = null; - } - - public synchronized void setBm(Bitmap abm) { - if (bm != null && bm != abm) - bm.recycle(); - bm = abm; - } - - public synchronized Bitmap getBm() { - return bm; - } -} diff --git a/android/src/com/artifex/mupdf/ChoosePDFActivity.java b/android/src/com/artifex/mupdf/ChoosePDFActivity.java deleted file mode 100644 index 44551d3c..00000000 --- a/android/src/com/artifex/mupdf/ChoosePDFActivity.java +++ /dev/null @@ -1,179 +0,0 @@ -package com.artifex.mupdf; - -import java.io.File; -import java.io.FileFilter; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Map; - -import android.app.AlertDialog; -import android.app.ListActivity; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.content.res.Resources; -import android.net.Uri; -import android.os.Bundle; -import android.os.Environment; -import android.os.FileObserver; -import android.os.Handler; -import android.view.View; -import android.widget.ListView; - -public class ChoosePDFActivity extends ListActivity { - static private File mDirectory; - static private Map mPositions = new HashMap(); - private File mParent; - private File [] mDirs; - private File [] mFiles; - private Handler mHandler; - private Runnable mUpdateFiles; - private ChoosePDFAdapter adapter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Resources res = getResources(); - String appName = res.getString(R.string.app_name); - String version = res.getString(R.string.version); - String title = res.getString(R.string.picker_title); - setTitle(String.format(title, appName, version)); - - String storageState = Environment.getExternalStorageState(); - - if (!Environment.MEDIA_MOUNTED.equals(storageState) - && !Environment.MEDIA_MOUNTED_READ_ONLY.equals(storageState)) - { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.no_media_warning); - builder.setMessage(R.string.no_media_hint); - AlertDialog alert = builder.create(); - alert.setButton(AlertDialog.BUTTON_POSITIVE,"Dismiss", - new OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - alert.show(); - return; - } - - if (mDirectory == null) - mDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); - - // Create a list adapter... - adapter = new ChoosePDFAdapter(getLayoutInflater()); - setListAdapter(adapter); - - // ...that is updated dynamically when files are scanned - mHandler = new Handler(); - mUpdateFiles = new Runnable() { - public void run() { - mParent = mDirectory.getParentFile(); - - mDirs = mDirectory.listFiles(new FileFilter() { - - public boolean accept(File file) { - return file.isDirectory(); - } - }); - if (mDirs == null) - mDirs = new File[0]; - - mFiles = mDirectory.listFiles(new FileFilter() { - - public boolean accept(File file) { - if (file.isDirectory()) - return false; - String fname = file.getName().toLowerCase(); - if (fname.endsWith(".pdf")) - return true; - if (fname.endsWith(".xps")) - return true; - if (fname.endsWith(".cbz")) - return true; - return false; - } - }); - if (mFiles == null) - mFiles = new File[0]; - - Arrays.sort(mFiles, new Comparator() { - public int compare(File arg0, File arg1) { - return arg0.getName().compareToIgnoreCase(arg1.getName()); - } - }); - - Arrays.sort(mDirs, new Comparator() { - public int compare(File arg0, File arg1) { - return arg0.getName().compareToIgnoreCase(arg1.getName()); - } - }); - - adapter.clear(); - if (mParent != null) - adapter.add(new ChoosePDFItem(ChoosePDFItem.Type.PARENT, "..")); - for (File f : mDirs) - adapter.add(new ChoosePDFItem(ChoosePDFItem.Type.DIR, f.getName())); - for (File f : mFiles) - adapter.add(new ChoosePDFItem(ChoosePDFItem.Type.DOC, f.getName())); - - lastPosition(); - } - }; - - // Start initial file scan... - mHandler.post(mUpdateFiles); - - // ...and observe the directory and scan files upon changes. - FileObserver observer = new FileObserver(mDirectory.getPath(), FileObserver.CREATE | FileObserver.DELETE) { - public void onEvent(int event, String path) { - mHandler.post(mUpdateFiles); - } - }; - observer.startWatching(); - } - - private void lastPosition() { - String p = mDirectory.getAbsolutePath(); - if (mPositions.containsKey(p)) - getListView().setSelection(mPositions.get(p)); - } - - @Override - protected void onListItemClick(ListView l, View v, int position, long id) { - super.onListItemClick(l, v, position, id); - - mPositions.put(mDirectory.getAbsolutePath(), getListView().getFirstVisiblePosition()); - - if (position < (mParent == null ? 0 : 1)) { - mDirectory = mParent; - mHandler.post(mUpdateFiles); - return; - } - - position -= (mParent == null ? 0 : 1); - - if (position < mDirs.length) { - mDirectory = mDirs[position]; - mHandler.post(mUpdateFiles); - return; - } - - position -= mDirs.length; - - Uri uri = Uri.parse(mFiles[position].getAbsolutePath()); - Intent intent = new Intent(this,MuPDFActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.setData(uri); - startActivity(intent); - } - - @Override - protected void onPause() { - super.onPause(); - mPositions.put(mDirectory.getAbsolutePath(), getListView().getFirstVisiblePosition()); - } -} diff --git a/android/src/com/artifex/mupdf/ChoosePDFAdapter.java b/android/src/com/artifex/mupdf/ChoosePDFAdapter.java deleted file mode 100644 index 15e4a2fe..00000000 --- a/android/src/com/artifex/mupdf/ChoosePDFAdapter.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.artifex.mupdf; - -import java.util.LinkedList; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -public class ChoosePDFAdapter extends BaseAdapter { - private final LinkedList mItems; - private final LayoutInflater mInflater; - - public ChoosePDFAdapter(LayoutInflater inflater) { - mInflater = inflater; - mItems = new LinkedList(); - } - - public void clear() { - mItems.clear(); - } - - public void add(ChoosePDFItem item) { - mItems.add(item); - notifyDataSetChanged(); - } - - public int getCount() { - return mItems.size(); - } - - public Object getItem(int i) { - return null; - } - - public long getItemId(int arg0) { - return 0; - } - - private int iconForType(ChoosePDFItem.Type type) { - switch (type) { - case PARENT: return R.drawable.ic_arrow_up; - case DIR: return R.drawable.ic_dir; - case DOC: return R.drawable.ic_doc; - default: return 0; - } - } - - public View getView(int position, View convertView, ViewGroup parent) { - View v; - if (convertView == null) { - v = mInflater.inflate(R.layout.picker_entry, null); - } else { - v = convertView; - } - ChoosePDFItem item = mItems.get(position); - ((TextView)v.findViewById(R.id.name)).setText(item.name); - ((ImageView)v.findViewById(R.id.icon)).setBackgroundResource(iconForType(item.type)); - return v; - } - -} diff --git a/android/src/com/artifex/mupdf/ChoosePDFItem.java b/android/src/com/artifex/mupdf/ChoosePDFItem.java deleted file mode 100644 index d9c2bb37..00000000 --- a/android/src/com/artifex/mupdf/ChoosePDFItem.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.artifex.mupdf; - -public class ChoosePDFItem { - enum Type { - PARENT, DIR, DOC - } - - final public Type type; - final public String name; - - public ChoosePDFItem (Type t, String n) { - type = t; - name = n; - } -} diff --git a/android/src/com/artifex/mupdf/LinkInfo.java b/android/src/com/artifex/mupdf/LinkInfo.java deleted file mode 100644 index 8b52d7f7..00000000 --- a/android/src/com/artifex/mupdf/LinkInfo.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.artifex.mupdf; - -import android.graphics.RectF; - -public class LinkInfo { - final public RectF rect; - - public LinkInfo(float l, float t, float r, float b) { - rect = new RectF(l, t, r, b); - } - - public void acceptVisitor(LinkInfoVisitor visitor) { - } -} \ No newline at end of file diff --git a/android/src/com/artifex/mupdf/LinkInfoExternal.java b/android/src/com/artifex/mupdf/LinkInfoExternal.java deleted file mode 100644 index fce1eae0..00000000 --- a/android/src/com/artifex/mupdf/LinkInfoExternal.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.artifex.mupdf; - -public class LinkInfoExternal extends LinkInfo { - final public String url; - - public LinkInfoExternal(float l, float t, float r, float b, String u) { - super(l, t, r, b); - url = u; - } - - public void acceptVisitor(LinkInfoVisitor visitor) { - visitor.visitExternal(this); - } -} diff --git a/android/src/com/artifex/mupdf/LinkInfoInternal.java b/android/src/com/artifex/mupdf/LinkInfoInternal.java deleted file mode 100644 index 9d34fcb7..00000000 --- a/android/src/com/artifex/mupdf/LinkInfoInternal.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.artifex.mupdf; - -public class LinkInfoInternal extends LinkInfo { - final public int pageNumber; - - public LinkInfoInternal(float l, float t, float r, float b, int p) { - super(l, t, r, b); - pageNumber = p; - } - - public void acceptVisitor(LinkInfoVisitor visitor) { - visitor.visitInternal(this); - } -} diff --git a/android/src/com/artifex/mupdf/LinkInfoRemote.java b/android/src/com/artifex/mupdf/LinkInfoRemote.java deleted file mode 100644 index 7d5c302f..00000000 --- a/android/src/com/artifex/mupdf/LinkInfoRemote.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.artifex.mupdf; - -public class LinkInfoRemote extends LinkInfo { - final public String fileSpec; - final public int pageNumber; - final public boolean newWindow; - - public LinkInfoRemote(float l, float t, float r, float b, String f, int p, boolean n) { - super(l, t, r, b); - fileSpec = f; - pageNumber = p; - newWindow = n; - } - - public void acceptVisitor(LinkInfoVisitor visitor) { - visitor.visitRemote(this); - } -} diff --git a/android/src/com/artifex/mupdf/LinkInfoVisitor.java b/android/src/com/artifex/mupdf/LinkInfoVisitor.java deleted file mode 100644 index 13dd863d..00000000 --- a/android/src/com/artifex/mupdf/LinkInfoVisitor.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.artifex.mupdf; - -abstract public class LinkInfoVisitor { - public abstract void visitInternal(LinkInfoInternal li); - public abstract void visitExternal(LinkInfoExternal li); - public abstract void visitRemote(LinkInfoRemote li); -} diff --git a/android/src/com/artifex/mupdf/MuPDFActivity.java b/android/src/com/artifex/mupdf/MuPDFActivity.java deleted file mode 100644 index 0f20f580..00000000 --- a/android/src/com/artifex/mupdf/MuPDFActivity.java +++ /dev/null @@ -1,1031 +0,0 @@ -package com.artifex.mupdf; - -import java.util.concurrent.Executor; - -import android.animation.Animator; -import android.animation.AnimatorInflater; -import android.animation.AnimatorSet; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.graphics.Color; -import android.graphics.RectF; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.text.Editable; -import android.text.TextWatcher; -import android.text.method.PasswordTransformationMethod; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.TranslateAnimation; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.RelativeLayout; -import android.widget.SeekBar; -import android.widget.TextView; -import android.widget.ViewAnimator; - -class ThreadPerTaskExecutor implements Executor { - public void execute(Runnable r) { - new Thread(r).start(); - } -} - -class SearchTaskResult { - public final String txt; - public final int pageNumber; - public final RectF searchBoxes[]; - static private SearchTaskResult singleton; - - SearchTaskResult(String _txt, int _pageNumber, RectF _searchBoxes[]) { - txt = _txt; - pageNumber = _pageNumber; - searchBoxes = _searchBoxes; - } - - static public SearchTaskResult get() { - return singleton; - } - - static public void set(SearchTaskResult r) { - singleton = r; - } -} - -class ProgressDialogX extends ProgressDialog { - public ProgressDialogX(Context context) { - super(context); - } - - private boolean mCancelled = false; - - public boolean isCancelled() { - return mCancelled; - } - - @Override - public void cancel() { - mCancelled = true; - super.cancel(); - } -} - -public class MuPDFActivity extends Activity -{ - /* The core rendering instance */ - private final int TAP_PAGE_MARGIN = 5; - private static final int SEARCH_PROGRESS_DELAY = 200; - private MuPDFCore core; - private String mFileName; - private ReaderView mDocView; - private View mButtonsView; - private boolean mButtonsVisible; - private EditText mPasswordView; - private TextView mFilenameView; - private SeekBar mPageSlider; - private int mPageSliderRes; - private TextView mPageNumberView; - private TextView mInfoView; - private ImageButton mSearchButton; - private ImageButton mSelectButton; - private ImageButton mCancelSelectButton; - private ImageButton mCopySelectButton; - private ImageButton mCancelButton; - private ImageButton mOutlineButton; - private ViewAnimator mTopBarSwitcher; - private ImageButton mLinkButton; - private boolean mTopBarIsSearch; - private ImageButton mSearchBack; - private ImageButton mSearchFwd; - private EditText mSearchText; - private AsyncTask mSearchTask; - //private SearchTaskResult mSearchTaskResult; - private AlertDialog.Builder mAlertBuilder; - private boolean mLinkHighlight = false; - private boolean mSelecting = false; - private final Handler mHandler = new Handler(); - private boolean mAlertsActive= false; - private AsyncTask mAlertTask; - private AlertDialog mAlertDialog; - - public void createAlertWaiter() { - mAlertsActive = true; - // All mupdf library calls are performed on asynchronous tasks to avoid stalling - // the UI. Some calls can lead to javascript-invoked requests to display an - // alert dialog and collect a reply from the user. The task has to be blocked - // until the user's reply is received. This method creates an asynchronous task, - // the purpose of which is to wait of these requests and produce the dialog - // in response, while leaving the core blocked. When the dialog receives the - // user's response, it is sent to the core via replyToAlert, unblocking it. - // Another alert-waiting task is then created to pick up the next alert. - if (mAlertTask != null) { - mAlertTask.cancel(true); - mAlertTask = null; - } - if (mAlertDialog != null) { - mAlertDialog.cancel(); - mAlertDialog = null; - } - mAlertTask = new AsyncTask() { - - @Override - protected MuPDFAlert doInBackground(Void... arg0) { - if (!mAlertsActive) - return null; - - return core.waitForAlert(); - } - - @Override - protected void onPostExecute(final MuPDFAlert result) { - // core.waitForAlert may return null when shutting down - if (result == null) - return; - final MuPDFAlert.ButtonPressed pressed[] = new MuPDFAlert.ButtonPressed[3]; - for(int i = 0; i < 3; i++) - pressed[i] = MuPDFAlert.ButtonPressed.None; - DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - mAlertDialog = null; - if (mAlertsActive) { - int index = 0; - switch (which) { - case AlertDialog.BUTTON1: index=0; break; - case AlertDialog.BUTTON2: index=1; break; - case AlertDialog.BUTTON3: index=2; break; - } - result.buttonPressed = pressed[index]; - // Send the user's response to the core, so that it can - // continue processing. - core.replyToAlert(result); - // Create another alert-waiter to pick up the next alert. - createAlertWaiter(); - } - } - }; - mAlertDialog = mAlertBuilder.create(); - mAlertDialog.setTitle(result.title); - mAlertDialog.setMessage(result.message); - switch (result.iconType) - { - case Error: - break; - case Warning: - break; - case Question: - break; - case Status: - break; - } - switch (result.buttonGroupType) - { - case OkCancel: - mAlertDialog.setButton(AlertDialog.BUTTON2, "Cancel", listener); - pressed[1] = MuPDFAlert.ButtonPressed.Cancel; - case Ok: - mAlertDialog.setButton(AlertDialog.BUTTON1, "Ok", listener); - pressed[0] = MuPDFAlert.ButtonPressed.Ok; - break; - case YesNoCancel: - mAlertDialog.setButton(AlertDialog.BUTTON3, "Cancel", listener); - pressed[2] = MuPDFAlert.ButtonPressed.Cancel; - case YesNo: - mAlertDialog.setButton(AlertDialog.BUTTON1, "Yes", listener); - pressed[0] = MuPDFAlert.ButtonPressed.Yes; - mAlertDialog.setButton(AlertDialog.BUTTON2, "No", listener); - pressed[1] = MuPDFAlert.ButtonPressed.No; - break; - } - mAlertDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - mAlertDialog = null; - if (mAlertsActive) { - result.buttonPressed = MuPDFAlert.ButtonPressed.None; - core.replyToAlert(result); - createAlertWaiter(); - } - } - }); - - mAlertDialog.show(); - } - }; - - mAlertTask.executeOnExecutor(new ThreadPerTaskExecutor()); - } - - public void destroyAlertWaiter() { - mAlertsActive = false; - if (mAlertDialog != null) { - mAlertDialog.cancel(); - mAlertDialog = null; - } - if (mAlertTask != null) { - mAlertTask.cancel(true); - mAlertTask = null; - } - } - - private MuPDFCore openFile(String path) - { - int lastSlashPos = path.lastIndexOf('/'); - mFileName = new String(lastSlashPos == -1 - ? path - : path.substring(lastSlashPos+1)); - System.out.println("Trying to open "+path); - try - { - core = new MuPDFCore(path); - // New file: drop the old outline data - OutlineActivityData.set(null); - } - catch (Exception e) - { - System.out.println(e); - return null; - } - return core; - } - - /** Called when the activity is first created. */ - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - mAlertBuilder = new AlertDialog.Builder(this); - - if (core == null) { - core = (MuPDFCore)getLastNonConfigurationInstance(); - - if (savedInstanceState != null && savedInstanceState.containsKey("FileName")) { - mFileName = savedInstanceState.getString("FileName"); - } - } - if (core == null) { - Intent intent = getIntent(); - if (Intent.ACTION_VIEW.equals(intent.getAction())) { - Uri uri = intent.getData(); - if (uri.toString().startsWith("content://")) { - // Handle view requests from the Transformer Prime's file manager - // Hopefully other file managers will use this same scheme, if not - // using explicit paths. - Cursor cursor = getContentResolver().query(uri, new String[]{"_data"}, null, null, null); - if (cursor.moveToFirst()) { - uri = Uri.parse(cursor.getString(0)); - } - } - core = openFile(Uri.decode(uri.getEncodedPath())); - SearchTaskResult.set(null); - } - if (core != null && core.needsPassword()) { - requestPassword(savedInstanceState); - return; - } - } - if (core == null) - { - AlertDialog alert = mAlertBuilder.create(); - alert.setTitle(R.string.open_failed); - alert.setButton(AlertDialog.BUTTON_POSITIVE, "Dismiss", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - alert.show(); - return; - } - - createUI(savedInstanceState); - } - - public void requestPassword(final Bundle savedInstanceState) { - mPasswordView = new EditText(this); - mPasswordView.setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); - mPasswordView.setTransformationMethod(new PasswordTransformationMethod()); - - AlertDialog alert = mAlertBuilder.create(); - alert.setTitle(R.string.enter_password); - alert.setView(mPasswordView); - alert.setButton(AlertDialog.BUTTON_POSITIVE, "Ok", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (core.authenticatePassword(mPasswordView.getText().toString())) { - createUI(savedInstanceState); - } else { - requestPassword(savedInstanceState); - } - } - }); - alert.setButton(AlertDialog.BUTTON_NEGATIVE, "Cancel", - new DialogInterface.OnClickListener() { - - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - alert.show(); - } - - public void createUI(Bundle savedInstanceState) { - if (core == null) - return; - - // Now create the UI. - // First create the document view making use of the ReaderView's internal - // gesture recognition - mDocView = new ReaderView(this) { - private boolean showButtonsDisabled; - - public boolean onSingleTapUp(MotionEvent e) { - LinkInfo link = null; - - if (!mSelecting && !showButtonsDisabled) { - MuPDFPageView pageView = (MuPDFPageView) mDocView.getDisplayedView(); - if (MuPDFCore.javascriptSupported() && pageView.passClickEvent(e.getX(), e.getY())) { - // If the page consumes the event do nothing else - } else if (mLinkHighlight && pageView != null && (link = pageView.hitLink(e.getX(), e.getY())) != null) { - link.acceptVisitor(new LinkInfoVisitor() { - @Override - public void visitInternal(LinkInfoInternal li) { - // Clicked on an internal (GoTo) link - mDocView.setDisplayedViewIndex(li.pageNumber); - } - - @Override - public void visitExternal(LinkInfoExternal li) { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(li.url)); - startActivity(intent); - } - - @Override - public void visitRemote(LinkInfoRemote li) { - // Clicked on a remote (GoToR) link - } - }); - } else if (e.getX() < super.getWidth()/TAP_PAGE_MARGIN) { - super.moveToPrevious(); - } else if (e.getX() > super.getWidth()*(TAP_PAGE_MARGIN-1)/TAP_PAGE_MARGIN) { - super.moveToNext(); - } else if (!mButtonsVisible) { - showButtons(); - } else { - hideButtons(); - } - } - return super.onSingleTapUp(e); - } - - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (!mSelecting) { - if (!showButtonsDisabled) - hideButtons(); - - return super.onScroll(e1, e2, distanceX, distanceY); - } else { - MuPDFPageView pageView = (MuPDFPageView) mDocView.getDisplayedView(); - if (pageView != null) - pageView.selectText(e1.getX(), e1.getY(), e2.getX(), e2.getY()); - return true; - } - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, - float velocityX, float velocityY) { - if (!mSelecting) - return super.onFling(e1, e2, velocityX, velocityY); - else - return true; - } - - public boolean onScaleBegin(ScaleGestureDetector d) { - // Disabled showing the buttons until next touch. - // Not sure why this is needed, but without it - // pinch zoom can make the buttons appear - showButtonsDisabled = true; - return super.onScaleBegin(d); - } - - public boolean onTouchEvent(MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) - showButtonsDisabled = false; - - return super.onTouchEvent(event); - } - - protected void onChildSetup(int i, View v) { - if (SearchTaskResult.get() != null && SearchTaskResult.get().pageNumber == i) - ((PageView)v).setSearchBoxes(SearchTaskResult.get().searchBoxes); - else - ((PageView)v).setSearchBoxes(null); - - ((PageView)v).setLinkHighlighting(mLinkHighlight); - - ((MuPDFPageView)v).setChangeReporter(new Runnable() { - public void run() { - mDocView.applyToChildren(new ReaderView.ViewMapper() { - @Override - void applyToView(View view) { - ((MuPDFPageView)view).update(); - } - }); - } - }); - } - - protected void onMoveToChild(int i) { - if (core == null) - return; - mPageNumberView.setText(String.format("%d / %d", i+1, core.countPages())); - mPageSlider.setMax((core.countPages()-1) * mPageSliderRes); - mPageSlider.setProgress(i * mPageSliderRes); - if (SearchTaskResult.get() != null && SearchTaskResult.get().pageNumber != i) { - SearchTaskResult.set(null); - mDocView.resetupChildren(); - } - } - - protected void onSettle(View v) { - // When the layout has settled ask the page to render - // in HQ - ((PageView)v).addHq(false); - } - - protected void onUnsettle(View v) { - // When something changes making the previous settled view - // no longer appropriate, tell the page to remove HQ - ((PageView)v).removeHq(); - } - - @Override - protected void onNotInUse(View v) { - ((PageView)v).releaseResources(); - } - }; - mDocView.setAdapter(new MuPDFPageAdapter(this, core)); - - // Make the buttons overlay, and store all its - // controls in variables - makeButtonsView(); - - // Set up the page slider - int smax = Math.max(core.countPages()-1,1); - mPageSliderRes = ((10 + smax - 1)/smax) * 2; - - // Set the file-name text - mFilenameView.setText(mFileName); - - // Activate the seekbar - mPageSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - public void onStopTrackingTouch(SeekBar seekBar) { - mDocView.setDisplayedViewIndex((seekBar.getProgress()+mPageSliderRes/2)/mPageSliderRes); - } - - public void onStartTrackingTouch(SeekBar seekBar) {} - - public void onProgressChanged(SeekBar seekBar, int progress, - boolean fromUser) { - updatePageNumView((progress+mPageSliderRes/2)/mPageSliderRes); - } - }); - - // Activate the search-preparing button - mSearchButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - searchModeOn(); - } - }); - - // Activate the select button - mSelectButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - mSelecting = true; - mTopBarSwitcher.setDisplayedChild(2); - } - }); - - mCancelSelectButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - PageView pageView = (PageView) mDocView.getDisplayedView(); - if (pageView != null) - pageView.deselectText(); - mSelecting = false; - mTopBarSwitcher.setDisplayedChild(0); - } - }); - - final Context context = this; - mCopySelectButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - PageView pageView = (PageView) mDocView.getDisplayedView(); - boolean copied = false; - if (pageView != null) - copied = pageView.copySelection(); - mSelecting = false; - mTopBarSwitcher.setDisplayedChild(0); - mInfoView.setText(copied?"Copied to clipboard":"No text selected"); - AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(context, R.animator.info); - set.setTarget(mInfoView); - set.addListener(new Animator.AnimatorListener() { - public void onAnimationStart(Animator animation) { - mInfoView.setVisibility(View.VISIBLE); - } - - public void onAnimationRepeat(Animator animation) { - } - - public void onAnimationEnd(Animator animation) { - mInfoView.setVisibility(View.INVISIBLE); - } - - public void onAnimationCancel(Animator animation) { - } - }); - set.start(); - } - }); - - mCancelButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - searchModeOff(); - } - }); - - // Search invoking buttons are disabled while there is no text specified - mSearchBack.setEnabled(false); - mSearchFwd.setEnabled(false); - mSearchBack.setColorFilter(Color.argb(255, 128, 128, 128)); - mSearchFwd.setColorFilter(Color.argb(255, 128, 128, 128)); - - // React to interaction with the text widget - mSearchText.addTextChangedListener(new TextWatcher() { - - public void afterTextChanged(Editable s) { - boolean haveText = s.toString().length() > 0; - mSearchBack.setEnabled(haveText); - mSearchFwd.setEnabled(haveText); - if (haveText) { - mSearchBack.setColorFilter(Color.argb(255, 255, 255, 255)); - mSearchFwd.setColorFilter(Color.argb(255, 255, 255, 255)); - } else { - mSearchBack.setColorFilter(Color.argb(255, 128, 128, 128)); - mSearchFwd.setColorFilter(Color.argb(255, 128, 128, 128)); - } - - // Remove any previous search results - if (SearchTaskResult.get() != null && !mSearchText.getText().toString().equals(SearchTaskResult.get().txt)) { - SearchTaskResult.set(null); - mDocView.resetupChildren(); - } - } - public void beforeTextChanged(CharSequence s, int start, int count, - int after) {} - public void onTextChanged(CharSequence s, int start, int before, - int count) {} - }); - - //React to Done button on keyboard - mSearchText.setOnEditorActionListener(new TextView.OnEditorActionListener() { - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_DONE) - search(1); - return false; - } - }); - - mSearchText.setOnKeyListener(new View.OnKeyListener() { - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) - search(1); - return false; - } - }); - - // Activate search invoking buttons - mSearchBack.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - search(-1); - } - }); - mSearchFwd.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - search(1); - } - }); - - mLinkButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - if (mLinkHighlight) { - mLinkButton.setColorFilter(Color.argb(0xFF, 255, 255, 255)); - mLinkHighlight = false; - } else { - // LINK_COLOR tint - mLinkButton.setColorFilter(Color.argb(0xFF, 172, 114, 37)); - mLinkHighlight = true; - } - // Inform pages of the change. - mDocView.resetupChildren(); - } - }); - - if (core.hasOutline()) { - mOutlineButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - OutlineItem outline[] = core.getOutline(); - if (outline != null) { - OutlineActivityData.get().items = outline; - Intent intent = new Intent(MuPDFActivity.this, OutlineActivity.class); - startActivityForResult(intent, 0); - } - } - }); - } else { - mOutlineButton.setVisibility(View.GONE); - } - - // Reenstate last state if it was recorded - SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); - mDocView.setDisplayedViewIndex(prefs.getInt("page"+mFileName, 0)); - - if (savedInstanceState == null || !savedInstanceState.getBoolean("ButtonsHidden", false)) - showButtons(); - - if(savedInstanceState != null && savedInstanceState.getBoolean("SearchMode", false)) - searchModeOn(); - - // Stick the document view and the buttons overlay into a parent view - RelativeLayout layout = new RelativeLayout(this); - layout.addView(mDocView); - layout.addView(mButtonsView); - layout.setBackgroundResource(R.drawable.tiled_background); - //layout.setBackgroundResource(R.color.canvas); - setContentView(layout); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode >= 0) - mDocView.setDisplayedViewIndex(resultCode); - super.onActivityResult(requestCode, resultCode, data); - } - - public Object onRetainNonConfigurationInstance() - { - MuPDFCore mycore = core; - core = null; - return mycore; - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - if (mFileName != null && mDocView != null) { - outState.putString("FileName", mFileName); - - // Store current page in the prefs against the file name, - // so that we can pick it up each time the file is loaded - // Other info is needed only for screen-orientation change, - // so it can go in the bundle - SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); - SharedPreferences.Editor edit = prefs.edit(); - edit.putInt("page"+mFileName, mDocView.getDisplayedViewIndex()); - edit.commit(); - } - - if (!mButtonsVisible) - outState.putBoolean("ButtonsHidden", true); - - if (mTopBarIsSearch) - outState.putBoolean("SearchMode", true); - } - - @Override - protected void onPause() { - super.onPause(); - - killSearch(); - - if (mFileName != null && mDocView != null) { - SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); - SharedPreferences.Editor edit = prefs.edit(); - edit.putInt("page"+mFileName, mDocView.getDisplayedViewIndex()); - edit.commit(); - } - } - - public void onDestroy() - { - if (core != null) - core.onDestroy(); - if (mAlertTask != null) { - mAlertTask.cancel(true); - mAlertTask = null; - } - core = null; - super.onDestroy(); - } - - void showButtons() { - if (core == null) - return; - if (!mButtonsVisible) { - mButtonsVisible = true; - // Update page number text and slider - int index = mDocView.getDisplayedViewIndex(); - updatePageNumView(index); - mPageSlider.setMax((core.countPages()-1)*mPageSliderRes); - mPageSlider.setProgress(index*mPageSliderRes); - if (mTopBarIsSearch) { - mSearchText.requestFocus(); - showKeyboard(); - } - - Animation anim = new TranslateAnimation(0, 0, -mTopBarSwitcher.getHeight(), 0); - anim.setDuration(200); - anim.setAnimationListener(new Animation.AnimationListener() { - public void onAnimationStart(Animation animation) { - mTopBarSwitcher.setVisibility(View.VISIBLE); - } - public void onAnimationRepeat(Animation animation) {} - public void onAnimationEnd(Animation animation) {} - }); - mTopBarSwitcher.startAnimation(anim); - - anim = new TranslateAnimation(0, 0, mPageSlider.getHeight(), 0); - anim.setDuration(200); - anim.setAnimationListener(new Animation.AnimationListener() { - public void onAnimationStart(Animation animation) { - mPageSlider.setVisibility(View.VISIBLE); - } - public void onAnimationRepeat(Animation animation) {} - public void onAnimationEnd(Animation animation) { - mPageNumberView.setVisibility(View.VISIBLE); - } - }); - mPageSlider.startAnimation(anim); - } - } - - void hideButtons() { - if (mButtonsVisible) { - mButtonsVisible = false; - hideKeyboard(); - - Animation anim = new TranslateAnimation(0, 0, 0, -mTopBarSwitcher.getHeight()); - anim.setDuration(200); - anim.setAnimationListener(new Animation.AnimationListener() { - public void onAnimationStart(Animation animation) {} - public void onAnimationRepeat(Animation animation) {} - public void onAnimationEnd(Animation animation) { - mTopBarSwitcher.setVisibility(View.INVISIBLE); - } - }); - mTopBarSwitcher.startAnimation(anim); - - anim = new TranslateAnimation(0, 0, 0, mPageSlider.getHeight()); - anim.setDuration(200); - anim.setAnimationListener(new Animation.AnimationListener() { - public void onAnimationStart(Animation animation) { - mPageNumberView.setVisibility(View.INVISIBLE); - } - public void onAnimationRepeat(Animation animation) {} - public void onAnimationEnd(Animation animation) { - mPageSlider.setVisibility(View.INVISIBLE); - } - }); - mPageSlider.startAnimation(anim); - } - } - - void searchModeOn() { - if (!mTopBarIsSearch) { - mTopBarIsSearch = true; - //Focus on EditTextWidget - mSearchText.requestFocus(); - showKeyboard(); - mTopBarSwitcher.setDisplayedChild(1); - } - } - - void searchModeOff() { - if (mTopBarIsSearch) { - mTopBarIsSearch = false; - hideKeyboard(); - mTopBarSwitcher.setDisplayedChild(0); - SearchTaskResult.set(null); - // Make the ReaderView act on the change to mSearchTaskResult - // via overridden onChildSetup method. - mDocView.resetupChildren(); - } - } - - void updatePageNumView(int index) { - if (core == null) - return; - mPageNumberView.setText(String.format("%d / %d", index+1, core.countPages())); - } - - void makeButtonsView() { - mButtonsView = getLayoutInflater().inflate(R.layout.buttons,null); - mFilenameView = (TextView)mButtonsView.findViewById(R.id.docNameText); - mPageSlider = (SeekBar)mButtonsView.findViewById(R.id.pageSlider); - mPageNumberView = (TextView)mButtonsView.findViewById(R.id.pageNumber); - mInfoView = (TextView)mButtonsView.findViewById(R.id.info); - mSearchButton = (ImageButton)mButtonsView.findViewById(R.id.searchButton); - mSelectButton = (ImageButton)mButtonsView.findViewById(R.id.selectButton); - mCancelSelectButton = (ImageButton)mButtonsView.findViewById(R.id.cancelSelectButton); - mCopySelectButton = (ImageButton)mButtonsView.findViewById(R.id.copySelectButton); - mCancelButton = (ImageButton)mButtonsView.findViewById(R.id.cancel); - mOutlineButton = (ImageButton)mButtonsView.findViewById(R.id.outlineButton); - mTopBarSwitcher = (ViewAnimator)mButtonsView.findViewById(R.id.switcher); - mSearchBack = (ImageButton)mButtonsView.findViewById(R.id.searchBack); - mSearchFwd = (ImageButton)mButtonsView.findViewById(R.id.searchForward); - mSearchText = (EditText)mButtonsView.findViewById(R.id.searchText); - mLinkButton = (ImageButton)mButtonsView.findViewById(R.id.linkButton); - mTopBarSwitcher.setVisibility(View.INVISIBLE); - mPageNumberView.setVisibility(View.INVISIBLE); - mInfoView.setVisibility(View.INVISIBLE); - mPageSlider.setVisibility(View.INVISIBLE); - } - - void showKeyboard() { - InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm != null) - imm.showSoftInput(mSearchText, 0); - } - - void hideKeyboard() { - InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm != null) - imm.hideSoftInputFromWindow(mSearchText.getWindowToken(), 0); - } - - void killSearch() { - if (mSearchTask != null) { - mSearchTask.cancel(true); - mSearchTask = null; - } - } - - void search(int direction) { - hideKeyboard(); - if (core == null) - return; - killSearch(); - - final int increment = direction; - final int startIndex = SearchTaskResult.get() == null ? mDocView.getDisplayedViewIndex() : SearchTaskResult.get().pageNumber + increment; - - final ProgressDialogX progressDialog = new ProgressDialogX(this); - progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - progressDialog.setTitle(getString(R.string.searching_)); - progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - killSearch(); - } - }); - progressDialog.setMax(core.countPages()); - - mSearchTask = new AsyncTask() { - @Override - protected SearchTaskResult doInBackground(Void... params) { - int index = startIndex; - - while (0 <= index && index < core.countPages() && !isCancelled()) { - publishProgress(index); - RectF searchHits[] = core.searchPage(index, mSearchText.getText().toString()); - - if (searchHits != null && searchHits.length > 0) - return new SearchTaskResult(mSearchText.getText().toString(), index, searchHits); - - index += increment; - } - return null; - } - - @Override - protected void onPostExecute(SearchTaskResult result) { - progressDialog.cancel(); - if (result != null) { - // Ask the ReaderView to move to the resulting page - mDocView.setDisplayedViewIndex(result.pageNumber); - SearchTaskResult.set(result); - // Make the ReaderView act on the change to mSearchTaskResult - // via overridden onChildSetup method. - mDocView.resetupChildren(); - } else { - mAlertBuilder.setTitle(SearchTaskResult.get() == null ? R.string.text_not_found : R.string.no_further_occurences_found); - AlertDialog alert = mAlertBuilder.create(); - alert.setButton(AlertDialog.BUTTON_POSITIVE, "Dismiss", - (DialogInterface.OnClickListener)null); - alert.show(); - } - } - - @Override - protected void onCancelled() { - super.onCancelled(); - progressDialog.cancel(); - } - - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - progressDialog.setProgress(values[0].intValue()); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - mHandler.postDelayed(new Runnable() { - public void run() { - if (!progressDialog.isCancelled()) - { - progressDialog.show(); - progressDialog.setProgress(startIndex); - } - } - }, SEARCH_PROGRESS_DELAY); - } - }; - - mSearchTask.execute(); - } - - @Override - public boolean onSearchRequested() { - if (mButtonsVisible && mTopBarIsSearch) { - hideButtons(); - } else { - showButtons(); - searchModeOn(); - } - return super.onSearchRequested(); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - if (mButtonsVisible && !mTopBarIsSearch) { - hideButtons(); - } else { - showButtons(); - searchModeOff(); - } - return super.onPrepareOptionsMenu(menu); - } - - @Override - protected void onStart() { - if (core != null) - core.startAlerts(); - - createAlertWaiter(); - super.onStart(); - } - - @Override - protected void onStop() { - destroyAlertWaiter(); - if (core != null) - core.stopAlerts(); - - super.onStop(); - } - - @Override - public void onBackPressed() { - if (core.hasChanges()) { - DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (which == AlertDialog.BUTTON_POSITIVE) - core.save(); - - finish(); - } - }; - AlertDialog alert = mAlertBuilder.create(); - alert.setTitle("MuPDF"); - alert.setMessage("Document has changes. Save them?"); - alert.setButton(AlertDialog.BUTTON_POSITIVE, "Yes", listener); - alert.setButton(AlertDialog.BUTTON_NEGATIVE, "No", listener); - alert.show(); - } else { - super.onBackPressed(); - } - } -} diff --git a/android/src/com/artifex/mupdf/MuPDFAlert.java b/android/src/com/artifex/mupdf/MuPDFAlert.java deleted file mode 100644 index c024ad44..00000000 --- a/android/src/com/artifex/mupdf/MuPDFAlert.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.artifex.mupdf; - -public class MuPDFAlert { - public enum IconType {Error,Warning,Question,Status}; - public enum ButtonPressed {None,Ok,Cancel,No,Yes}; - public enum ButtonGroupType {Ok,OkCancel,YesNo,YesNoCancel}; - - public final String message; - public final IconType iconType; - public final ButtonGroupType buttonGroupType; - public final String title; - public ButtonPressed buttonPressed; - - MuPDFAlert(String aMessage, IconType aIconType, ButtonGroupType aButtonGroupType, String aTitle, ButtonPressed aButtonPressed) { - message = aMessage; - iconType = aIconType; - buttonGroupType = aButtonGroupType; - title = aTitle; - buttonPressed = aButtonPressed; - } -} diff --git a/android/src/com/artifex/mupdf/MuPDFAlertInternal.java b/android/src/com/artifex/mupdf/MuPDFAlertInternal.java deleted file mode 100644 index d1e93701..00000000 --- a/android/src/com/artifex/mupdf/MuPDFAlertInternal.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.artifex.mupdf; - -// Version of MuPDFAlert without enums to simplify JNI -public class MuPDFAlertInternal { - public final String message; - public final int iconType; - public final int buttonGroupType; - public final String title; - public int buttonPressed; - - MuPDFAlertInternal(String aMessage, int aIconType, int aButtonGroupType, String aTitle, int aButtonPressed) { - message = aMessage; - iconType = aIconType; - buttonGroupType = aButtonGroupType; - title = aTitle; - buttonPressed = aButtonPressed; - } - - MuPDFAlertInternal(MuPDFAlert alert) { - message = alert.message; - iconType = alert.iconType.ordinal(); - buttonGroupType = alert.buttonGroupType.ordinal(); - title = alert.message; - buttonPressed = alert.buttonPressed.ordinal(); - } - - MuPDFAlert toAlert() { - return new MuPDFAlert(message, MuPDFAlert.IconType.values()[iconType], MuPDFAlert.ButtonGroupType.values()[buttonGroupType], title, MuPDFAlert.ButtonPressed.values()[buttonPressed]); - } -} diff --git a/android/src/com/artifex/mupdf/MuPDFCore.java b/android/src/com/artifex/mupdf/MuPDFCore.java deleted file mode 100644 index bcef1605..00000000 --- a/android/src/com/artifex/mupdf/MuPDFCore.java +++ /dev/null @@ -1,251 +0,0 @@ -package com.artifex.mupdf; -import java.util.ArrayList; - -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.PointF; -import android.graphics.RectF; - -public class MuPDFCore -{ - /* load our native library */ - static { - System.loadLibrary("mupdf"); - } - - /* Readable members */ - private int numPages = -1; - private float pageWidth; - private float pageHeight; - private long globals; - - /* The native functions */ - private native long openFile(String filename); - private native int countPagesInternal(); - private native void gotoPageInternal(int localActionPageNum); - private native float getPageWidth(); - private native float getPageHeight(); - private native void drawPage(Bitmap bitmap, - int pageW, int pageH, - int patchX, int patchY, - int patchW, int patchH); - private native void updatePageInternal(Bitmap bitmap, - int page, - int pageW, int pageH, - int patchX, int patchY, - int patchW, int patchH); - private native RectF[] searchPage(String text); - private native TextChar[][][][] text(); - private native int passClickEventInternal(int page, float x, float y); - private native void setFocusedWidgetChoiceSelectedInternal(String [] selected); - private native String [] getFocusedWidgetChoiceSelected(); - private native String [] getFocusedWidgetChoiceOptions(); - private native int setFocusedWidgetTextInternal(String text); - private native String getFocusedWidgetTextInternal(); - private native int getFocusedWidgetTypeInternal(); - private native LinkInfo [] getPageLinksInternal(int page); - private native RectF[] getWidgetAreasInternal(int page); - private native OutlineItem [] getOutlineInternal(); - private native boolean hasOutlineInternal(); - private native boolean needsPasswordInternal(); - private native boolean authenticatePasswordInternal(String password); - private native MuPDFAlertInternal waitForAlertInternal(); - private native void replyToAlertInternal(MuPDFAlertInternal alert); - private native void startAlertsInternal(); - private native void stopAlertsInternal(); - private native void destroying(); - private native boolean hasChangesInternal(); - private native void saveInternal(); - - public static native boolean javascriptSupported(); - - public MuPDFCore(String filename) throws Exception - { - globals = openFile(filename); - if (globals == 0) - { - throw new Exception("Failed to open "+filename); - } - } - - public int countPages() - { - if (numPages < 0) - numPages = countPagesSynchronized(); - - return numPages; - } - - private synchronized int countPagesSynchronized() { - return countPagesInternal(); - } - - /* Shim function */ - private void gotoPage(int page) - { - if (page > numPages-1) - page = numPages-1; - else if (page < 0) - page = 0; - gotoPageInternal(page); - this.pageWidth = getPageWidth(); - this.pageHeight = getPageHeight(); - } - - public synchronized PointF getPageSize(int page) { - gotoPage(page); - return new PointF(pageWidth, pageHeight); - } - - public MuPDFAlert waitForAlert() { - MuPDFAlertInternal alert = waitForAlertInternal(); - return alert != null ? alert.toAlert() : null; - } - - public void replyToAlert(MuPDFAlert alert) { - replyToAlertInternal(new MuPDFAlertInternal(alert)); - } - - public void stopAlerts() { - stopAlertsInternal(); - } - - public void startAlerts() { - startAlertsInternal(); - } - - public synchronized void onDestroy() { - destroying(); - globals = 0; - } - - public synchronized Bitmap drawPage(BitmapHolder h, int page, - int pageW, int pageH, - int patchX, int patchY, - int patchW, int patchH) { - gotoPage(page); - // Clear the reference out before creating the new bitmap - h.setBm(null); - Bitmap bm = Bitmap.createBitmap(patchW, patchH, Config.ARGB_8888); - drawPage(bm, pageW, pageH, patchX, patchY, patchW, patchH); - return bm; - } - - public synchronized Bitmap updatePage(BitmapHolder h, int page, - int pageW, int pageH, - int patchX, int patchY, - int patchW, int patchH) { - Bitmap bm = null; - Bitmap old_bm = h.getBm(); - - if (old_bm == null) - return null; - - bm = old_bm.copy(Bitmap.Config.ARGB_8888, false); - old_bm = null; - - updatePageInternal(bm, page, pageW, pageH, patchX, patchY, patchW, patchH); - return bm; - } - - public synchronized PassClickResult passClickEvent(int page, float x, float y) { - boolean changed = passClickEventInternal(page, x, y) != 0; - - switch (WidgetType.values()[getFocusedWidgetTypeInternal()]) - { - case TEXT: - return new PassClickResultText(changed, getFocusedWidgetTextInternal()); - case LISTBOX: - case COMBOBOX: - return new PassClickResultChoice(changed, getFocusedWidgetChoiceOptions(), getFocusedWidgetChoiceSelected()); - default: - return new PassClickResult(changed); - } - - } - - public synchronized boolean setFocusedWidgetText(int page, String text) { - boolean success; - gotoPage(page); - success = setFocusedWidgetTextInternal(text) != 0 ? true : false; - - return success; - } - - public synchronized void setFocusedWidgetChoiceSelected(String [] selected) { - setFocusedWidgetChoiceSelectedInternal(selected); - } - - public synchronized LinkInfo [] getPageLinks(int page) { - return getPageLinksInternal(page); - } - - public synchronized RectF [] getWidgetAreas(int page) { - return getWidgetAreasInternal(page); - } - - public synchronized RectF [] searchPage(int page, String text) { - gotoPage(page); - return searchPage(text); - } - - public synchronized TextWord [][] textLines(int page) { - gotoPage(page); - TextChar[][][][] chars = text(); - - // The text of the page held in a hierarchy (blocks, lines, spans). - // Currently we don't need to distinguish the blocks level or - // the spans, and we need to collect the text into words. - ArrayList lns = new ArrayList(); - - for (TextChar[][][] bl: chars) { - for (TextChar[][] ln: bl) { - ArrayList wds = new ArrayList(); - TextWord wd = new TextWord(); - - for (TextChar[] sp: ln) { - for (TextChar tc: sp) { - if (tc.c != ' ') { - wd.Add(tc); - } else if (wd.w.length() > 0) { - wds.add(wd); - wd = new TextWord(); - } - } - } - - if (wd.w.length() > 0) - wds.add(wd); - - if (wds.size() > 0) - lns.add(wds.toArray(new TextWord[wds.size()])); - } - } - - return lns.toArray(new TextWord[lns.size()][]); - } - - public synchronized boolean hasOutline() { - return hasOutlineInternal(); - } - - public synchronized OutlineItem [] getOutline() { - return getOutlineInternal(); - } - - public synchronized boolean needsPassword() { - return needsPasswordInternal(); - } - - public synchronized boolean authenticatePassword(String password) { - return authenticatePasswordInternal(password); - } - - public synchronized boolean hasChanges() { - return hasChangesInternal(); - } - - public synchronized void save() { - saveInternal(); - } -} diff --git a/android/src/com/artifex/mupdf/MuPDFPageAdapter.java b/android/src/com/artifex/mupdf/MuPDFPageAdapter.java deleted file mode 100644 index c509cde0..00000000 --- a/android/src/com/artifex/mupdf/MuPDFPageAdapter.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.artifex.mupdf; - -import android.content.Context; -import android.graphics.Point; -import android.graphics.PointF; -import android.util.SparseArray; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; - -public class MuPDFPageAdapter extends BaseAdapter { - private final Context mContext; - private final MuPDFCore mCore; - private final SparseArray mPageSizes = new SparseArray(); - - public MuPDFPageAdapter(Context c, MuPDFCore core) { - mContext = c; - mCore = core; - } - - public int getCount() { - return mCore.countPages(); - } - - public Object getItem(int position) { - return null; - } - - public long getItemId(int position) { - return 0; - } - - public View getView(final int position, View convertView, ViewGroup parent) { - final MuPDFPageView pageView; - if (convertView == null) { - pageView = new MuPDFPageView(mContext, mCore, new Point(parent.getWidth(), parent.getHeight())); - } else { - pageView = (MuPDFPageView) convertView; - } - - PointF pageSize = mPageSizes.get(position); - if (pageSize != null) { - // We already know the page size. Set it up - // immediately - pageView.setPage(position, pageSize); - } else { - // Page size as yet unknown. Blank it for now, and - // start a background task to find the size - pageView.blank(position); - AsyncTask sizingTask = new AsyncTask() { - @Override - protected PointF doInBackground(Void... arg0) { - return mCore.getPageSize(position); - } - - @Override - protected void onPostExecute(PointF result) { - super.onPostExecute(result); - // We now know the page size - mPageSizes.put(position, result); - // Check that this view hasn't been reused for - // another page since we started - if (pageView.getPage() == position) - pageView.setPage(position, result); - } - }; - - sizingTask.execute((Void)null); - } - return pageView; - } -} diff --git a/android/src/com/artifex/mupdf/MuPDFPageView.java b/android/src/com/artifex/mupdf/MuPDFPageView.java deleted file mode 100644 index 3e4ba70f..00000000 --- a/android/src/com/artifex/mupdf/MuPDFPageView.java +++ /dev/null @@ -1,243 +0,0 @@ -package com.artifex.mupdf; - -import android.app.AlertDialog; -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.view.LayoutInflater; -import android.view.WindowManager; -import android.widget.EditText; - -abstract class PassClickResultVisitor { - public abstract void visitText(PassClickResultText result); - public abstract void visitChoice(PassClickResultChoice 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); - } -} - -public class MuPDFPageView extends PageView { - private final MuPDFCore mCore; - private AsyncTask mPassClick; - private RectF mWidgetAreas[]; - private AsyncTask mLoadWidgetAreas; - private AlertDialog.Builder mTextEntryBuilder; - private AlertDialog.Builder mChoiceEntryBuilder; - private AlertDialog mTextEntry; - private EditText mEditText; - private AsyncTask mSetWidgetText; - private AsyncTask mSetWidgetChoice; - private Runnable changeReporter; - - public MuPDFPageView(Context c, MuPDFCore core, Point parentSize) { - super(c, parentSize); - mCore = core; - mTextEntryBuilder = new AlertDialog.Builder(c); - mTextEntryBuilder.setTitle("MuPDF: 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("Cancel", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }); - mTextEntryBuilder.setPositiveButton("Okay", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - mSetWidgetText = new AsyncTask () { - @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("MuPDF: choose value"); - } - - 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() { - @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(); - } - - public void setChangeReporter(Runnable reporter) { - changeReporter = reporter; - } - - public boolean 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 hitWidget = false; - - if (mWidgetAreas != null) { - for (int i = 0; i < mWidgetAreas.length && !hitWidget; i++) - if (mWidgetAreas[i].contains(docRelX, docRelY)) - hitWidget = true; - } - - if (hitWidget) { - mPassClick = new AsyncTask() { - @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); - } - }); - } - }; - - mPassClick.execute(); - } - - return hitWidget; - } - - @Override - protected Bitmap drawPage(BitmapHolder h, int sizeX, int sizeY, - int patchX, int patchY, int patchWidth, int patchHeight) { - return mCore.drawPage(h, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight); - } - - @Override - protected Bitmap updatePage(BitmapHolder h, int sizeX, int sizeY, - int patchX, int patchY, int patchWidth, int patchHeight) { - return mCore.updatePage(h, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight); - } - - @Override - protected LinkInfo[] getLinkInfo() { - return mCore.getPageLinks(mPageNumber); - } - - @Override - protected TextWord[][] getText() { - return mCore.textLines(mPageNumber); - } - - @Override - public void setPage(final int page, PointF size) { - mLoadWidgetAreas = new AsyncTask () { - @Override - protected RectF[] doInBackground(Void... arg0) { - return mCore.getWidgetAreas(page); - } - - @Override - protected void onPostExecute(RectF[] result) { - mWidgetAreas = result; - } - }; - - mLoadWidgetAreas.execute(); - - super.setPage(page, size); - } -} diff --git a/android/src/com/artifex/mupdf/OutlineActivity.java b/android/src/com/artifex/mupdf/OutlineActivity.java deleted file mode 100644 index 262d4e22..00000000 --- a/android/src/com/artifex/mupdf/OutlineActivity.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.artifex.mupdf; - -import android.app.ListActivity; -import android.os.Bundle; -import android.view.View; -import android.widget.ListView; - -public class OutlineActivity extends ListActivity { - OutlineItem mItems[]; - /** Called when the activity is first created. */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mItems = OutlineActivityData.get().items; - setListAdapter(new OutlineAdapter(getLayoutInflater(),mItems)); - // Restore the position within the list from last viewing - getListView().setSelection(OutlineActivityData.get().position); - getListView().setDividerHeight(0); - setResult(-1); - } - - @Override - protected void onListItemClick(ListView l, View v, int position, long id) { - super.onListItemClick(l, v, position, id); - OutlineActivityData.get().position = getListView().getFirstVisiblePosition(); - setResult(mItems[position].page); - finish(); - } -} diff --git a/android/src/com/artifex/mupdf/OutlineActivityData.java b/android/src/com/artifex/mupdf/OutlineActivityData.java deleted file mode 100644 index abf60eba..00000000 --- a/android/src/com/artifex/mupdf/OutlineActivityData.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.artifex.mupdf; - -public class OutlineActivityData { - public OutlineItem items[]; - public int position; - static private OutlineActivityData singleton; - - static public void set(OutlineActivityData d) { - singleton = d; - } - - static public OutlineActivityData get() { - if (singleton == null) - singleton = new OutlineActivityData(); - return singleton; - } -} diff --git a/android/src/com/artifex/mupdf/OutlineAdapter.java b/android/src/com/artifex/mupdf/OutlineAdapter.java deleted file mode 100644 index ad1d6621..00000000 --- a/android/src/com/artifex/mupdf/OutlineAdapter.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.artifex.mupdf; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -public class OutlineAdapter extends BaseAdapter { - private final OutlineItem mItems[]; - private final LayoutInflater mInflater; - public OutlineAdapter(LayoutInflater inflater, OutlineItem items[]) { - mInflater = inflater; - mItems = items; - } - - public int getCount() { - return mItems.length; - } - - public Object getItem(int arg0) { - return null; - } - - public long getItemId(int arg0) { - return 0; - } - - public View getView(int position, View convertView, ViewGroup parent) { - View v; - if (convertView == null) { - v = mInflater.inflate(R.layout.outline_entry, null); - } else { - v = convertView; - } - int level = mItems[position].level; - if (level > 8) level = 8; - String space = ""; - for (int i=0; i lines = new ArrayList(); - for (TextWord[] line : mText) - if (line[0].bottom > mSelectBox.top && line[0].top < mSelectBox.bottom) - lines.add(line); - - Iterator it = lines.iterator(); - while (it.hasNext()) { - TextWord[] line = it.next(); - boolean firstLine = line[0].top < mSelectBox.top; - boolean lastLine = line[0].bottom > mSelectBox.bottom; - float start = Float.NEGATIVE_INFINITY; - float end = Float.POSITIVE_INFINITY; - - if (firstLine && lastLine) { - start = Math.min(mSelectBox.left, mSelectBox.right); - end = Math.max(mSelectBox.left, mSelectBox.right); - } else if (firstLine) { - start = mSelectBox.left; - } else if (lastLine) { - end = mSelectBox.right; - } - - onStartLine(); - - for (TextWord word : line) - if (word.right > start && word.left < end) - onWord(word); - - onEndLine(); - } - } -} - -public abstract class PageView extends ViewGroup { - private static final int HIGHLIGHT_COLOR = 0x802572AC; - private static final int LINK_COLOR = 0x80AC7225; - private static final int BACKGROUND_COLOR = 0xFFFFFFFF; - private static final int PROGRESS_DIALOG_DELAY = 200; - private final Context mContext; - protected int mPageNumber; - private Point mParentSize; - protected Point mSize; // Size of page at minimum zoom - protected float mSourceScale; - - private ImageView mEntire; // Image rendered at minimum zoom - private BitmapHolder mEntireBmh; - private AsyncTask mGetText; - private AsyncTask mGetLinkInfo; - private AsyncTask mDrawEntire; - - private Point mPatchViewSize; // View size on the basis of which the patch was created - private Rect mPatchArea; - private ImageView mPatch; - private BitmapHolder mPatchBmh; - private AsyncTask mDrawPatch; - private RectF mSearchBoxes[]; - protected LinkInfo mLinks[]; - private RectF mSelectBox; - private TextWord mText[][]; - private View mSearchView; - private boolean mIsBlank; - private boolean mHighlightLinks; - - private ProgressBar mBusyIndicator; - private final Handler mHandler = new Handler(); - - public PageView(Context c, Point parentSize) { - super(c); - mContext = c; - mParentSize = parentSize; - setBackgroundColor(BACKGROUND_COLOR); - mEntireBmh = new BitmapHolder(); - mPatchBmh = new BitmapHolder(); - } - - protected abstract Bitmap drawPage(BitmapHolder h, int sizeX, int sizeY, int patchX, int patchY, int patchWidth, int patchHeight); - protected abstract Bitmap updatePage(BitmapHolder h, int sizeX, int sizeY, int patchX, int patchY, int patchWidth, int patchHeight); - protected abstract LinkInfo[] getLinkInfo(); - protected abstract TextWord[][] getText(); - - private void reinit() { - // Cancel pending render task - if (mDrawEntire != null) { - mDrawEntire.cancel(true); - mDrawEntire = null; - } - - if (mDrawPatch != null) { - mDrawPatch.cancel(true); - mDrawPatch = null; - } - - if (mGetLinkInfo != null) { - mGetLinkInfo.cancel(true); - mGetLinkInfo = null; - } - - if (mGetText != null) { - mGetText.cancel(true); - mGetText = null; - } - - mIsBlank = true; - mPageNumber = 0; - - if (mSize == null) - mSize = mParentSize; - - if (mEntire != null) { - mEntire.setImageBitmap(null); - mEntireBmh.setBm(null); - } - - if (mPatch != null) { - mPatch.setImageBitmap(null); - mPatchBmh.setBm(null); - } - - mPatchViewSize = null; - mPatchArea = null; - - mSearchBoxes = null; - mLinks = null; - mSelectBox = null; - mText = null; - } - - public void releaseResources() { - reinit(); - - if (mBusyIndicator != null) { - removeView(mBusyIndicator); - mBusyIndicator = null; - } - } - - public void blank(int page) { - reinit(); - mPageNumber = page; - - if (mBusyIndicator == null) { - mBusyIndicator = new ProgressBar(mContext); - mBusyIndicator.setIndeterminate(true); - mBusyIndicator.setBackgroundResource(R.drawable.busy); - addView(mBusyIndicator); - } - } - - public void setPage(int page, PointF size) { - // Cancel pending render task - if (mDrawEntire != null) { - mDrawEntire.cancel(true); - mDrawEntire = null; - } - - mIsBlank = false; - - mPageNumber = page; - if (mEntire == null) { - mEntire = new OpaqueImageView(mContext); - mEntire.setScaleType(ImageView.ScaleType.FIT_CENTER); - addView(mEntire); - } - - // Calculate scaled size that fits within the screen limits - // This is the size at minimum zoom - mSourceScale = Math.min(mParentSize.x/size.x, mParentSize.y/size.y); - Point newSize = new Point((int)(size.x*mSourceScale), (int)(size.y*mSourceScale)); - mSize = newSize; - - mEntire.setImageBitmap(null); - mEntireBmh.setBm(null); - - // Get the link info in the background - mGetLinkInfo = new AsyncTask() { - protected LinkInfo[] doInBackground(Void... v) { - return getLinkInfo(); - } - - protected void onPostExecute(LinkInfo[] v) { - mLinks = v; - invalidate(); - } - }; - - mGetLinkInfo.execute(); - - // Render the page in the background - mDrawEntire = new AsyncTask() { - protected Bitmap doInBackground(Void... v) { - return drawPage(mEntireBmh, mSize.x, mSize.y, 0, 0, mSize.x, mSize.y); - } - - protected void onPreExecute() { - mEntire.setImageBitmap(null); - mEntireBmh.setBm(null); - - if (mBusyIndicator == null) { - mBusyIndicator = new ProgressBar(mContext); - mBusyIndicator.setIndeterminate(true); - mBusyIndicator.setBackgroundResource(R.drawable.busy); - addView(mBusyIndicator); - mBusyIndicator.setVisibility(INVISIBLE); - mHandler.postDelayed(new Runnable() { - public void run() { - if (mBusyIndicator != null) - mBusyIndicator.setVisibility(VISIBLE); - } - }, PROGRESS_DIALOG_DELAY); - } - } - - protected void onPostExecute(Bitmap bm) { - removeView(mBusyIndicator); - mBusyIndicator = null; - mEntire.setImageBitmap(bm); - mEntireBmh.setBm(bm); - invalidate(); - } - }; - - mDrawEntire.execute(); - - if (mSearchView == null) { - mSearchView = new View(mContext) { - @Override - protected void onDraw(final Canvas canvas) { - super.onDraw(canvas); - // Work out current total scale factor - // from source to view - final float scale = mSourceScale*(float)getWidth()/(float)mSize.x; - final Paint paint = new Paint(); - - if (!mIsBlank && mSearchBoxes != null) { - paint.setColor(HIGHLIGHT_COLOR); - for (RectF rect : mSearchBoxes) - canvas.drawRect(rect.left*scale, rect.top*scale, - rect.right*scale, rect.bottom*scale, - paint); - } - - if (!mIsBlank && mLinks != null && mHighlightLinks) { - paint.setColor(LINK_COLOR); - for (LinkInfo link : mLinks) - canvas.drawRect(link.rect.left*scale, link.rect.top*scale, - link.rect.right*scale, link.rect.bottom*scale, - paint); - } - - if (mSelectBox != null && mText != null) { - paint.setColor(HIGHLIGHT_COLOR); - TextSelector sel = new TextSelector(mText, mSelectBox) { - RectF rect; - - @Override - protected void onStartLine() { - rect = new RectF(); - } - - @Override - protected void onWord(TextWord word) { - rect.union(word); - } - - @Override - protected void onEndLine() { - if (!rect.isEmpty()) - canvas.drawRect(rect.left*scale, rect.top*scale, rect.right*scale, rect.bottom*scale, paint); - } - }; - - sel.select(); - } - } - }; - - addView(mSearchView); - } - requestLayout(); - } - - public void setSearchBoxes(RectF searchBoxes[]) { - mSearchBoxes = searchBoxes; - if (mSearchView != null) - mSearchView.invalidate(); - } - - public void setLinkHighlighting(boolean f) { - mHighlightLinks = f; - if (mSearchView != null) - mSearchView.invalidate(); - } - - public void deselectText() { - mSelectBox = null; - mSearchView.invalidate(); - } - - public void selectText(float x0, float y0, float x1, float y1) { - float scale = mSourceScale*(float)getWidth()/(float)mSize.x; - float docRelX0 = (x0 - getLeft())/scale; - float docRelY0 = (y0 - getTop())/scale; - float docRelX1 = (x1 - getLeft())/scale; - float docRelY1 = (y1 - getTop())/scale; - // Order on Y but maintain the point grouping - if (docRelY0 <= docRelY1) - mSelectBox = new RectF(docRelX0, docRelY0, docRelX1, docRelY1); - else - mSelectBox = new RectF(docRelX1, docRelY1, docRelX0, docRelY0); - - mSearchView.invalidate(); - - if (mGetText == null) { - mGetText = new AsyncTask() { - @Override - protected TextWord[][] doInBackground(Void... params) { - return getText(); - } - @Override - protected void onPostExecute(TextWord[][] result) { - mText = result; - mSearchView.invalidate(); - } - }; - - mGetText.execute(); - } - } - - public boolean copySelection() { - final StringBuilder text = new StringBuilder(); - - TextSelector sel = new TextSelector(mText, mSelectBox) { - StringBuilder line; - - @Override - protected void onStartLine() { - line = new StringBuilder(); - } - - @Override - protected void onWord(TextWord word) { - if (line.length() > 0) - line.append(' '); - line.append(word.w); - } - - @Override - protected void onEndLine() { - if (text.length() > 0) - text.append('\n'); - text.append(line); - } - }; - - sel.select(); - - if (text.length() == 0) - return false; - - ClipboardManager cm = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); - - cm.setPrimaryClip(ClipData.newPlainText("MuPDF", text)); - - mSelectBox = null; - mSearchView.invalidate(); - - return true; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int x, y; - switch(View.MeasureSpec.getMode(widthMeasureSpec)) { - case View.MeasureSpec.UNSPECIFIED: - x = mSize.x; - break; - default: - x = View.MeasureSpec.getSize(widthMeasureSpec); - } - switch(View.MeasureSpec.getMode(heightMeasureSpec)) { - case View.MeasureSpec.UNSPECIFIED: - y = mSize.y; - break; - default: - y = View.MeasureSpec.getSize(heightMeasureSpec); - } - - setMeasuredDimension(x, y); - - if (mBusyIndicator != null) { - int limit = Math.min(mParentSize.x, mParentSize.y)/2; - mBusyIndicator.measure(View.MeasureSpec.AT_MOST | limit, View.MeasureSpec.AT_MOST | limit); - } - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - int w = right-left; - int h = bottom-top; - - if (mEntire != null) { - mEntire.layout(0, 0, w, h); - } - - if (mSearchView != null) { - mSearchView.layout(0, 0, w, h); - } - - if (mPatchViewSize != null) { - if (mPatchViewSize.x != w || mPatchViewSize.y != h) { - // Zoomed since patch was created - mPatchViewSize = null; - mPatchArea = null; - if (mPatch != null) { - mPatch.setImageBitmap(null); - mPatchBmh.setBm(null); - } - } else { - mPatch.layout(mPatchArea.left, mPatchArea.top, mPatchArea.right, mPatchArea.bottom); - } - } - - if (mBusyIndicator != null) { - int bw = mBusyIndicator.getMeasuredWidth(); - int bh = mBusyIndicator.getMeasuredHeight(); - - mBusyIndicator.layout((w-bw)/2, (h-bh)/2, (w+bw)/2, (h+bh)/2); - } - } - - public void addHq(boolean update) { - Rect viewArea = new Rect(getLeft(),getTop(),getRight(),getBottom()); - // If the viewArea's size matches the unzoomed size, there is no need for an hq patch - if (viewArea.width() != mSize.x || viewArea.height() != mSize.y) { - Point patchViewSize = new Point(viewArea.width(), viewArea.height()); - Rect patchArea = new Rect(0, 0, mParentSize.x, mParentSize.y); - - // Intersect and test that there is an intersection - if (!patchArea.intersect(viewArea)) - return; - - // Offset patch area to be relative to the view top left - patchArea.offset(-viewArea.left, -viewArea.top); - - boolean area_unchanged = patchArea.equals(mPatchArea) && patchViewSize.equals(mPatchViewSize); - - // If being asked for the same area as last time and not because of an update then nothing to do - if (area_unchanged && !update) - return; - - boolean completeRedraw = !(area_unchanged && update); - - // Stop the drawing of previous patch if still going - if (mDrawPatch != null) { - mDrawPatch.cancel(true); - mDrawPatch = null; - } - - if (completeRedraw) { - // The bitmap holder mPatchBm may still be rendered to by a - // previously invoked task, and possibly for a different - // area, so we cannot risk the bitmap generated by this task - // being passed to it - mPatchBmh.setBm(null); - mPatchBmh = new BitmapHolder(); - } - - // Create and add the image view if not already done - if (mPatch == null) { - mPatch = new OpaqueImageView(mContext); - mPatch.setScaleType(ImageView.ScaleType.FIT_CENTER); - addView(mPatch); - mSearchView.bringToFront(); - } - - mDrawPatch = new AsyncTask() { - protected PatchInfo doInBackground(PatchInfo... v) { - if (v[0].completeRedraw) { - v[0].bm = drawPage(v[0].bmh, v[0].patchViewSize.x, v[0].patchViewSize.y, - v[0].patchArea.left, v[0].patchArea.top, - v[0].patchArea.width(), v[0].patchArea.height()); - } else { - v[0].bm = updatePage(v[0].bmh, v[0].patchViewSize.x, v[0].patchViewSize.y, - v[0].patchArea.left, v[0].patchArea.top, - v[0].patchArea.width(), v[0].patchArea.height()); - } - - return v[0]; - } - - protected void onPostExecute(PatchInfo v) { - if (mPatchBmh == v.bmh) { - mPatchViewSize = v.patchViewSize; - mPatchArea = v.patchArea; - if (v.bm != null) { - mPatch.setImageBitmap(v.bm); - v.bmh.setBm(v.bm); - v.bm = null; - } - //requestLayout(); - // Calling requestLayout here doesn't lead to a later call to layout. No idea - // why, but apparently others have run into the problem. - mPatch.layout(mPatchArea.left, mPatchArea.top, mPatchArea.right, mPatchArea.bottom); - invalidate(); - } - } - }; - - mDrawPatch.execute(new PatchInfo(patchViewSize, patchArea, mPatchBmh, completeRedraw)); - } - } - - public void update() { - // Cancel pending render task - if (mDrawEntire != null) { - mDrawEntire.cancel(true); - mDrawEntire = null; - } - - if (mDrawPatch != null) { - mDrawPatch.cancel(true); - mDrawPatch = null; - } - - // Render the page in the background - mDrawEntire = new AsyncTask() { - protected Bitmap doInBackground(Void... v) { - // Pass the current bitmap as a basis for the update, but use a bitmap - // holder so that the held bitmap will be nulled and not hold on to - // memory, should this view become redundant. - return updatePage(mEntireBmh, mSize.x, mSize.y, 0, 0, mSize.x, mSize.y); - } - - protected void onPostExecute(Bitmap bm) { - if (bm != null) { - mEntire.setImageBitmap(bm); - mEntireBmh.setBm(bm); - } - invalidate(); - } - }; - - mDrawEntire.execute(); - - addHq(true); - } - - public void removeHq() { - // Stop the drawing of the patch if still going - if (mDrawPatch != null) { - mDrawPatch.cancel(true); - mDrawPatch = null; - } - - // And get rid of it - mPatchViewSize = null; - mPatchArea = null; - if (mPatch != null) { - mPatch.setImageBitmap(null); - mPatchBmh.setBm(null); - } - } - - public int getPage() { - return mPageNumber; - } - - @Override - public boolean isOpaque() { - return true; - } -} diff --git a/android/src/com/artifex/mupdf/ReaderView.java b/android/src/com/artifex/mupdf/ReaderView.java deleted file mode 100644 index 3f62d2aa..00000000 --- a/android/src/com/artifex/mupdf/ReaderView.java +++ /dev/null @@ -1,583 +0,0 @@ -package com.artifex.mupdf; - -import java.util.LinkedList; -import java.util.NoSuchElementException; - -import android.content.Context; -import android.graphics.Point; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.util.SparseArray; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; -import android.view.View; -import android.widget.Adapter; -import android.widget.AdapterView; -import android.widget.Scroller; - -public class ReaderView extends AdapterView - implements GestureDetector.OnGestureListener, - ScaleGestureDetector.OnScaleGestureListener, - Runnable { - private static final int MOVING_DIAGONALLY = 0; - private static final int MOVING_LEFT = 1; - private static final int MOVING_RIGHT = 2; - private static final int MOVING_UP = 3; - private static final int MOVING_DOWN = 4; - - private static final int FLING_MARGIN = 100; - private static final int GAP = 20; - - private static final float MIN_SCALE = 1.0f; - private static final float MAX_SCALE = 5.0f; - - private Adapter mAdapter; - private int mCurrent; // Adapter's index for the current view - private boolean mResetLayout; - private final SparseArray - mChildViews = new SparseArray(3); - // Shadows the children of the adapter view - // but with more sensible indexing - private final LinkedList - mViewCache = new LinkedList(); - private boolean mUserInteracting; // Whether the user is interacting - private boolean mScaling; // Whether the user is currently pinch zooming - private float mScale = 1.0f; - private int mXScroll; // Scroll amounts recorded from events. - private int mYScroll; // and then accounted for in onLayout - private final GestureDetector - mGestureDetector; - private final ScaleGestureDetector - mScaleGestureDetector; - private final Scroller mScroller; - private int mScrollerLastX; - private int mScrollerLastY; - private boolean mScrollDisabled; - - static abstract class ViewMapper { - abstract void applyToView(View view); - } - - public ReaderView(Context context) { - super(context); - mGestureDetector = new GestureDetector(this); - mScaleGestureDetector = new ScaleGestureDetector(context, this); - mScroller = new Scroller(context); - } - - public ReaderView(Context context, AttributeSet attrs) { - super(context, attrs); - mGestureDetector = new GestureDetector(this); - mScaleGestureDetector = new ScaleGestureDetector(context, this); - mScroller = new Scroller(context); - } - - public ReaderView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mGestureDetector = new GestureDetector(this); - mScaleGestureDetector = new ScaleGestureDetector(context, this); - mScroller = new Scroller(context); - } - - public int getDisplayedViewIndex() { - return mCurrent; - } - - public void setDisplayedViewIndex(int i) { - if (0 <= i && i < mAdapter.getCount()) { - mCurrent = i; - onMoveToChild(i); - mResetLayout = true; - requestLayout(); - } - } - - public void moveToNext() { - View v = mChildViews.get(mCurrent+1); - if (v != null) - slideViewOntoScreen(v); - } - - public void moveToPrevious() { - View v = mChildViews.get(mCurrent-1); - if (v != null) - slideViewOntoScreen(v); - } - - public void resetupChildren() { - for (int i = 0; i < mChildViews.size(); i++) - onChildSetup(mChildViews.keyAt(i), mChildViews.valueAt(i)); - } - - public void applyToChildren(ViewMapper mapper) { - for (int i = 0; i < mChildViews.size(); i++) - mapper.applyToView(mChildViews.valueAt(i)); - } - - protected void onChildSetup(int i, View v) {} - - protected void onMoveToChild(int i) {} - - protected void onSettle(View v) {}; - - protected void onUnsettle(View v) {}; - - protected void onNotInUse(View v) {}; - - public View getDisplayedView() { - return mChildViews.get(mCurrent); - } - - public void run() { - if (!mScroller.isFinished()) { - mScroller.computeScrollOffset(); - int x = mScroller.getCurrX(); - int y = mScroller.getCurrY(); - mXScroll += x - mScrollerLastX; - mYScroll += y - mScrollerLastY; - mScrollerLastX = x; - mScrollerLastY = y; - requestLayout(); - post(this); - } - else if (!mUserInteracting) { - // End of an inertial scroll and the user is not interacting. - // The layout is stable - View v = mChildViews.get(mCurrent); - if (v != null) - postSettle(v); - } - } - - public boolean onDown(MotionEvent arg0) { - mScroller.forceFinished(true); - return true; - } - - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, - float velocityY) { - if (mScrollDisabled) - return true; - - View v = mChildViews.get(mCurrent); - if (v != null) { - Rect bounds = getScrollBounds(v); - switch(directionOfTravel(velocityX, velocityY)) { - case MOVING_LEFT: - if (bounds.left >= 0) { - // Fling off to the left bring next view onto screen - View vl = mChildViews.get(mCurrent+1); - - if (vl != null) { - slideViewOntoScreen(vl); - return true; - } - } - break; - case MOVING_RIGHT: - if (bounds.right <= 0) { - // Fling off to the right bring previous view onto screen - View vr = mChildViews.get(mCurrent-1); - - if (vr != null) { - slideViewOntoScreen(vr); - return true; - } - } - break; - } - mScrollerLastX = mScrollerLastY = 0; - // If the page has been dragged out of bounds then we want to spring back - // nicely. fling jumps back into bounds instantly, so we don't want to use - // fling in that case. On the other hand, we don't want to forgo a fling - // just because of a slightly off-angle drag taking us out of bounds other - // than in the direction of the drag, so we test for out of bounds only - // in the direction of travel. - // - // Also don't fling if out of bounds in any direction by more than fling - // margin - Rect expandedBounds = new Rect(bounds); - expandedBounds.inset(-FLING_MARGIN, -FLING_MARGIN); - - if(withinBoundsInDirectionOfTravel(bounds, velocityX, velocityY) - && expandedBounds.contains(0, 0)) { - mScroller.fling(0, 0, (int)velocityX, (int)velocityY, bounds.left, bounds.right, bounds.top, bounds.bottom); - post(this); - } - } - - return true; - } - - public void onLongPress(MotionEvent e) { - } - - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, - float distanceY) { - if (!mScrollDisabled) { - mXScroll -= distanceX; - mYScroll -= distanceY; - requestLayout(); - } - return true; - } - - public void onShowPress(MotionEvent e) { - } - - public boolean onSingleTapUp(MotionEvent e) { - return false; - } - - public boolean onScale(ScaleGestureDetector detector) { - float previousScale = mScale; - mScale = Math.min(Math.max(mScale * detector.getScaleFactor(), MIN_SCALE), MAX_SCALE); - float factor = mScale/previousScale; - - View v = mChildViews.get(mCurrent); - if (v != null) { - // Work out the focus point relative to the view top left - int viewFocusX = (int)detector.getFocusX() - (v.getLeft() + mXScroll); - int viewFocusY = (int)detector.getFocusY() - (v.getTop() + mYScroll); - // Scroll to maintain the focus point - mXScroll += viewFocusX - viewFocusX * factor; - mYScroll += viewFocusY - viewFocusY * factor; - requestLayout(); - } - return true; - } - - public boolean onScaleBegin(ScaleGestureDetector detector) { - mScaling = true; - // Ignore any scroll amounts yet to be accounted for: the - // screen is not showing the effect of them, so they can - // only confuse the user - mXScroll = mYScroll = 0; - // Avoid jump at end of scaling by disabling scrolling - // until the next start of gesture - mScrollDisabled = true; - return true; - } - - public void onScaleEnd(ScaleGestureDetector detector) { - mScaling = false; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - mScaleGestureDetector.onTouchEvent(event); - - if (!mScaling) - mGestureDetector.onTouchEvent(event); - - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mUserInteracting = true; - } - if (event.getActionMasked() == MotionEvent.ACTION_UP) { - mScrollDisabled = false; - mUserInteracting = false; - - View v = mChildViews.get(mCurrent); - if (v != null) { - if (mScroller.isFinished()) { - // If, at the end of user interaction, there is no - // current inertial scroll in operation then animate - // the view onto screen if necessary - slideViewOntoScreen(v); - } - - if (mScroller.isFinished()) { - // If still there is no inertial scroll in operation - // then the layout is stable - postSettle(v); - } - } - } - - requestLayout(); - return true; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - int n = getChildCount(); - for (int i = 0; i < n; i++) - measureView(getChildAt(i)); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, - int bottom) { - super.onLayout(changed, left, top, right, bottom); - - View cv = mChildViews.get(mCurrent); - Point cvOffset; - - if (!mResetLayout) { - // Move to next or previous if current is sufficiently off center - if (cv != null) { - cvOffset = subScreenSizeOffset(cv); - // cv.getRight() may be out of date with the current scale - // so add left to the measured width for the correct position - if (cv.getLeft() + cv.getMeasuredWidth() + cvOffset.x + GAP/2 + mXScroll < getWidth()/2 && mCurrent + 1 < mAdapter.getCount()) { - postUnsettle(cv); - // post to invoke test for end of animation - // where we must set hq area for the new current view - post(this); - - mCurrent++; - onMoveToChild(mCurrent); - } - - if (cv.getLeft() - cvOffset.x - GAP/2 + mXScroll >= getWidth()/2 && mCurrent > 0) { - postUnsettle(cv); - // post to invoke test for end of animation - // where we must set hq area for the new current view - post(this); - - mCurrent--; - onMoveToChild(mCurrent); - } - } - - // Remove not needed children and hold them for reuse - int numChildren = mChildViews.size(); - int childIndices[] = new int[numChildren]; - for (int i = 0; i < numChildren; i++) - childIndices[i] = mChildViews.keyAt(i); - - for (int i = 0; i < numChildren; i++) { - int ai = childIndices[i]; - if (ai < mCurrent - 1 || ai > mCurrent + 1) { - View v = mChildViews.get(ai); - onNotInUse(v); - mViewCache.add(v); - removeViewInLayout(v); - mChildViews.remove(ai); - } - } - } else { - mResetLayout = false; - mXScroll = mYScroll = 0; - - // Remove all children and hold them for reuse - int numChildren = mChildViews.size(); - for (int i = 0; i < numChildren; i++) { - View v = mChildViews.valueAt(i); - onNotInUse(v); - mViewCache.add(v); - removeViewInLayout(v); - } - mChildViews.clear(); - // post to ensure generation of hq area - post(this); - } - - // Ensure current view is present - int cvLeft, cvRight, cvTop, cvBottom; - boolean notPresent = (mChildViews.get(mCurrent) == null); - cv = getOrCreateChild(mCurrent); - // When the view is sub-screen-size in either dimension we - // offset it to center within the screen area, and to keep - // the views spaced out - cvOffset = subScreenSizeOffset(cv); - if (notPresent) { - //Main item not already present. Just place it top left - cvLeft = cvOffset.x; - cvTop = cvOffset.y; - } else { - // Main item already present. Adjust by scroll offsets - cvLeft = cv.getLeft() + mXScroll; - cvTop = cv.getTop() + mYScroll; - } - // Scroll values have been accounted for - mXScroll = mYScroll = 0; - cvRight = cvLeft + cv.getMeasuredWidth(); - cvBottom = cvTop + cv.getMeasuredHeight(); - - if (!mUserInteracting && mScroller.isFinished()) { - Point corr = getCorrection(getScrollBounds(cvLeft, cvTop, cvRight, cvBottom)); - cvRight += corr.x; - cvLeft += corr.x; - cvTop += corr.y; - cvBottom += corr.y; - } else if (cv.getMeasuredHeight() <= getHeight()) { - // When the current view is as small as the screen in height, clamp - // it vertically - Point corr = getCorrection(getScrollBounds(cvLeft, cvTop, cvRight, cvBottom)); - cvTop += corr.y; - cvBottom += corr.y; - } - - cv.layout(cvLeft, cvTop, cvRight, cvBottom); - - if (mCurrent > 0) { - View lv = getOrCreateChild(mCurrent - 1); - Point leftOffset = subScreenSizeOffset(lv); - int gap = leftOffset.x + GAP + cvOffset.x; - lv.layout(cvLeft - lv.getMeasuredWidth() - gap, - (cvBottom + cvTop - lv.getMeasuredHeight())/2, - cvLeft - gap, - (cvBottom + cvTop + lv.getMeasuredHeight())/2); - } - - if (mCurrent + 1 < mAdapter.getCount()) { - View rv = getOrCreateChild(mCurrent + 1); - Point rightOffset = subScreenSizeOffset(rv); - int gap = cvOffset.x + GAP + rightOffset.x; - rv.layout(cvRight + gap, - (cvBottom + cvTop - rv.getMeasuredHeight())/2, - cvRight + rv.getMeasuredWidth() + gap, - (cvBottom + cvTop + rv.getMeasuredHeight())/2); - } - - invalidate(); - } - - @Override - public Adapter getAdapter() { - return mAdapter; - } - - @Override - public View getSelectedView() { - throw new UnsupportedOperationException("Not supported"); - } - - @Override - public void setAdapter(Adapter adapter) { - mAdapter = adapter; - mChildViews.clear(); - removeAllViewsInLayout(); - requestLayout(); - } - - @Override - public void setSelection(int arg0) { - throw new UnsupportedOperationException("Not supported"); - } - - private View getCached() { - if (mViewCache.size() == 0) - return null; - else - return mViewCache.removeFirst(); - } - - private View getOrCreateChild(int i) { - View v = mChildViews.get(i); - if (v == null) { - v = mAdapter.getView(i, getCached(), this); - addAndMeasureChild(i, v); - } - onChildSetup(i, v); - - return v; - } - - private void addAndMeasureChild(int i, View v) { - LayoutParams params = v.getLayoutParams(); - if (params == null) { - params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - } - addViewInLayout(v, 0, params, true); - mChildViews.append(i, v); // Record the view against it's adapter index - measureView(v); - } - - private void measureView(View v) { - // See what size the view wants to be - v.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - // Work out a scale that will fit it to this view - float scale = Math.min((float)getWidth()/(float)v.getMeasuredWidth(), - (float)getHeight()/(float)v.getMeasuredHeight()); - // Use the fitting values scaled by our current scale factor - v.measure(View.MeasureSpec.EXACTLY | (int)(v.getMeasuredWidth()*scale*mScale), - View.MeasureSpec.EXACTLY | (int)(v.getMeasuredHeight()*scale*mScale)); - } - - private Rect getScrollBounds(int left, int top, int right, int bottom) { - int xmin = getWidth() - right; - int xmax = -left; - int ymin = getHeight() - bottom; - int ymax = -top; - - // In either dimension, if view smaller than screen then - // constrain it to be central - if (xmin > xmax) xmin = xmax = (xmin + xmax)/2; - if (ymin > ymax) ymin = ymax = (ymin + ymax)/2; - - return new Rect(xmin, ymin, xmax, ymax); - } - - private Rect getScrollBounds(View v) { - // There can be scroll amounts not yet accounted for in - // onLayout, so add mXScroll and mYScroll to the current - // positions when calculating the bounds. - return getScrollBounds(v.getLeft() + mXScroll, - v.getTop() + mYScroll, - v.getLeft() + v.getMeasuredWidth() + mXScroll, - v.getTop() + v.getMeasuredHeight() + mYScroll); - } - - private Point getCorrection(Rect bounds) { - return new Point(Math.min(Math.max(0,bounds.left),bounds.right), - Math.min(Math.max(0,bounds.top),bounds.bottom)); - } - - private void postSettle(final View v) { - // onSettle and onUnsettle are posted so that the calls - // wont be executed until after the system has performed - // layout. - post (new Runnable() { - public void run () { - onSettle(v); - } - }); - } - - private void postUnsettle(final View v) { - post (new Runnable() { - public void run () { - onUnsettle(v); - } - }); - } - - private void slideViewOntoScreen(View v) { - Point corr = getCorrection(getScrollBounds(v)); - if (corr.x != 0 || corr.y != 0) { - mScrollerLastX = mScrollerLastY = 0; - mScroller.startScroll(0, 0, corr.x, corr.y, 400); - post(this); - } - } - - private Point subScreenSizeOffset(View v) { - return new Point(Math.max((getWidth() - v.getMeasuredWidth())/2, 0), - Math.max((getHeight() - v.getMeasuredHeight())/2, 0)); - } - - private static int directionOfTravel(float vx, float vy) { - if (Math.abs(vx) > 2 * Math.abs(vy)) - return (vx > 0) ? MOVING_RIGHT : MOVING_LEFT; - else if (Math.abs(vy) > 2 * Math.abs(vx)) - return (vy > 0) ? MOVING_DOWN : MOVING_UP; - else - return MOVING_DIAGONALLY; - } - - private static boolean withinBoundsInDirectionOfTravel(Rect bounds, float vx, float vy) { - switch (directionOfTravel(vx, vy)) { - case MOVING_DIAGONALLY: return bounds.contains(0, 0); - case MOVING_LEFT: return bounds.left <= 0; - case MOVING_RIGHT: return bounds.right >= 0; - case MOVING_UP: return bounds.top <= 0; - case MOVING_DOWN: return bounds.bottom >= 0; - default: throw new NoSuchElementException(); - } - } -} diff --git a/android/src/com/artifex/mupdf/TextChar.java b/android/src/com/artifex/mupdf/TextChar.java deleted file mode 100644 index f51f8b5f..00000000 --- a/android/src/com/artifex/mupdf/TextChar.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.artifex.mupdf; - -import android.graphics.RectF; - -public class TextChar extends RectF { - public char c; - - public TextChar(float x0, float y0, float x1, float y1, char _c) { - super(x0, y0, x1, y1); - c = _c; - } -} diff --git a/android/src/com/artifex/mupdf/TextWord.java b/android/src/com/artifex/mupdf/TextWord.java deleted file mode 100644 index 9b1570f4..00000000 --- a/android/src/com/artifex/mupdf/TextWord.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.artifex.mupdf; - -import android.graphics.RectF; - -public class TextWord extends RectF { - public String w; - - public TextWord() { - super(); - w = new String(); - } - - public void Add(TextChar tc) { - super.union(tc); - w = w.concat(new String(new char[]{tc.c})); - } -} diff --git a/android/src/com/artifex/mupdf/WidgetType.java b/android/src/com/artifex/mupdf/WidgetType.java deleted file mode 100644 index 170232f4..00000000 --- a/android/src/com/artifex/mupdf/WidgetType.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.artifex.mupdf; - -public enum WidgetType { - NONE, - TEXT, - LISTBOX, - COMBOBOX -} diff --git a/android/src/com/artifex/mupdfdemo/AsyncTask.java b/android/src/com/artifex/mupdfdemo/AsyncTask.java new file mode 100644 index 00000000..67baf62a --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/AsyncTask.java @@ -0,0 +1,671 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.artifex.mupdfdemo; + +import java.util.ArrayDeque; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.FutureTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import android.os.Process; +import android.os.Handler; +import android.os.Message; + +/** + *

AsyncTask enables proper and easy use of the UI thread. This class allows to + * perform background operations and publish results on the UI thread without + * having to manipulate threads and/or handlers.

+ * + *

AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler} + * and does not constitute a generic threading framework. AsyncTasks should ideally be + * used for short operations (a few seconds at the most.) If you need to keep threads + * running for long periods of time, it is highly recommended you use the various APIs + * provided by the java.util.concurrent pacakge such as {@link Executor}, + * {@link ThreadPoolExecutor} and {@link FutureTask}.

+ * + *

An asynchronous task is defined by a computation that runs on a background thread and + * whose result is published on the UI thread. An asynchronous task is defined by 3 generic + * types, called Params, Progress and Result, + * and 4 steps, called onPreExecute, doInBackground, + * onProgressUpdate and onPostExecute.

+ * + *
+ *

Developer Guides

+ *

For more information about using tasks and threads, read the + * Processes and + * Threads developer guide.

+ *
+ * + *

Usage

+ *

AsyncTask must be subclassed to be used. The subclass will override at least + * one method ({@link #doInBackground}), and most often will override a + * second one ({@link #onPostExecute}.)

+ * + *

Here is an example of subclassing:

+ *
+ * private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
+ *     protected Long doInBackground(URL... urls) {
+ *         int count = urls.length;
+ *         long totalSize = 0;
+ *         for (int i = 0; i < count; i++) {
+ *             totalSize += Downloader.downloadFile(urls[i]);
+ *             publishProgress((int) ((i / (float) count) * 100));
+ *             // Escape early if cancel() is called
+ *             if (isCancelled()) break;
+ *         }
+ *         return totalSize;
+ *     }
+ *
+ *     protected void onProgressUpdate(Integer... progress) {
+ *         setProgressPercent(progress[0]);
+ *     }
+ *
+ *     protected void onPostExecute(Long result) {
+ *         showDialog("Downloaded " + result + " bytes");
+ *     }
+ * }
+ * 
+ * + *

Once created, a task is executed very simply:

+ *
+ * new DownloadFilesTask().execute(url1, url2, url3);
+ * 
+ * + *

AsyncTask's generic types

+ *

The three types used by an asynchronous task are the following:

+ *
    + *
  1. Params, the type of the parameters sent to the task upon + * execution.
  2. + *
  3. Progress, the type of the progress units published during + * the background computation.
  4. + *
  5. Result, the type of the result of the background + * computation.
  6. + *
+ *

Not all types are always used by an asynchronous task. To mark a type as unused, + * simply use the type {@link Void}:

+ *
+ * private class MyTask extends AsyncTask<Void, Void, Void> { ... }
+ * 
+ * + *

The 4 steps

+ *

When an asynchronous task is executed, the task goes through 4 steps:

+ *
    + *
  1. {@link #onPreExecute()}, invoked on the UI thread before the task + * is executed. This step is normally used to setup the task, for instance by + * showing a progress bar in the user interface.
  2. + *
  3. {@link #doInBackground}, invoked on the background thread + * immediately after {@link #onPreExecute()} finishes executing. This step is used + * to perform background computation that can take a long time. The parameters + * of the asynchronous task are passed to this step. The result of the computation must + * be returned by this step and will be passed back to the last step. This step + * can also use {@link #publishProgress} to publish one or more units + * of progress. These values are published on the UI thread, in the + * {@link #onProgressUpdate} step.
  4. + *
  5. {@link #onProgressUpdate}, invoked on the UI thread after a + * call to {@link #publishProgress}. The timing of the execution is + * undefined. This method is used to display any form of progress in the user + * interface while the background computation is still executing. For instance, + * it can be used to animate a progress bar or show logs in a text field.
  6. + *
  7. {@link #onPostExecute}, invoked on the UI thread after the background + * computation finishes. The result of the background computation is passed to + * this step as a parameter.
  8. + *
+ * + *

Cancelling a task

+ *

A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking + * this method will cause subsequent calls to {@link #isCancelled()} to return true. + * After invoking this method, {@link #onCancelled(Object)}, instead of + * {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])} + * returns. To ensure that a task is cancelled as quickly as possible, you should always + * check the return value of {@link #isCancelled()} periodically from + * {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)

+ * + *

Threading rules

+ *

There are a few threading rules that must be followed for this class to + * work properly:

+ *
    + *
  • The AsyncTask class must be loaded on the UI thread. This is done + * automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.
  • + *
  • The task instance must be created on the UI thread.
  • + *
  • {@link #execute} must be invoked on the UI thread.
  • + *
  • Do not call {@link #onPreExecute()}, {@link #onPostExecute}, + * {@link #doInBackground}, {@link #onProgressUpdate} manually.
  • + *
  • The task can be executed only once (an exception will be thrown if + * a second execution is attempted.)
  • + *
+ * + *

Memory observability

+ *

AsyncTask guarantees that all callback calls are synchronized in such a way that the following + * operations are safe without explicit synchronizations.

+ *
    + *
  • Set member fields in the constructor or {@link #onPreExecute}, and refer to them + * in {@link #doInBackground}. + *
  • Set member fields in {@link #doInBackground}, and refer to them in + * {@link #onProgressUpdate} and {@link #onPostExecute}. + *
+ * + *

Order of execution

+ *

When first introduced, AsyncTasks were executed serially on a single background + * thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed + * to a pool of threads allowing multiple tasks to operate in parallel. Starting with + * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single + * thread to avoid common application errors caused by parallel execution.

+ *

If you truly want parallel execution, you can invoke + * {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with + * {@link #THREAD_POOL_EXECUTOR}.

+ */ +public abstract class AsyncTask { + private static final String LOG_TAG = "AsyncTask"; + + private static final int CORE_POOL_SIZE = 5; + private static final int MAXIMUM_POOL_SIZE = 128; + private static final int KEEP_ALIVE = 1; + + private static final ThreadFactory sThreadFactory = new ThreadFactory() { + private final AtomicInteger mCount = new AtomicInteger(1); + + public Thread newThread(Runnable r) { + return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); + } + }; + + private static final BlockingQueue sPoolWorkQueue = + new LinkedBlockingQueue(10); + + /** + * An {@link Executor} that can be used to execute tasks in parallel. + */ + public static final Executor THREAD_POOL_EXECUTOR + = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, + TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); + + /** + * An {@link Executor} that executes tasks one at a time in serial + * order. This serialization is global to a particular process. + */ + public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); + + private static final int MESSAGE_POST_RESULT = 0x1; + private static final int MESSAGE_POST_PROGRESS = 0x2; + + private static final InternalHandler sHandler = new InternalHandler(); + + private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; + private final WorkerRunnable mWorker; + private final FutureTask mFuture; + + private volatile Status mStatus = Status.PENDING; + + private final AtomicBoolean mCancelled = new AtomicBoolean(); + private final AtomicBoolean mTaskInvoked = new AtomicBoolean(); + + private static class SerialExecutor implements Executor { + final ArrayDeque mTasks = new ArrayDeque(); + Runnable mActive; + + public synchronized void execute(final Runnable r) { + mTasks.offer(new Runnable() { + public void run() { + try { + r.run(); + } finally { + scheduleNext(); + } + } + }); + if (mActive == null) { + scheduleNext(); + } + } + + protected synchronized void scheduleNext() { + if ((mActive = mTasks.poll()) != null) { + THREAD_POOL_EXECUTOR.execute(mActive); + } + } + } + + /** + * Indicates the current status of the task. Each status will be set only once + * during the lifetime of a task. + */ + public enum Status { + /** + * Indicates that the task has not been executed yet. + */ + PENDING, + /** + * Indicates that the task is running. + */ + RUNNING, + /** + * Indicates that {@link AsyncTask#onPostExecute} has finished. + */ + FINISHED, + } + + /** @hide Used to force static handler to be created. */ + public static void init() { + sHandler.getLooper(); + } + + /** @hide */ + public static void setDefaultExecutor(Executor exec) { + sDefaultExecutor = exec; + } + + /** + * Creates a new asynchronous task. This constructor must be invoked on the UI thread. + */ + public AsyncTask() { + mWorker = new WorkerRunnable() { + public Result call() throws Exception { + mTaskInvoked.set(true); + + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + //noinspection unchecked + return postResult(doInBackground(mParams)); + } + }; + + mFuture = new FutureTask(mWorker) { + @Override + protected void done() { + try { + postResultIfNotInvoked(get()); + } catch (InterruptedException e) { + android.util.Log.w(LOG_TAG, e); + } catch (ExecutionException e) { + throw new RuntimeException("An error occured while executing doInBackground()", + e.getCause()); + } catch (CancellationException e) { + postResultIfNotInvoked(null); + } + } + }; + } + + private void postResultIfNotInvoked(Result result) { + final boolean wasTaskInvoked = mTaskInvoked.get(); + if (!wasTaskInvoked) { + postResult(result); + } + } + + private Result postResult(Result result) { + @SuppressWarnings("unchecked") + Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, + new AsyncTaskResult(this, result)); + message.sendToTarget(); + return result; + } + + /** + * Returns the current status of this task. + * + * @return The current status. + */ + public final Status getStatus() { + return mStatus; + } + + /** + * Override this method to perform a computation on a background thread. The + * specified parameters are the parameters passed to {@link #execute} + * by the caller of this task. + * + * This method can call {@link #publishProgress} to publish updates + * on the UI thread. + * + * @param params The parameters of the task. + * + * @return A result, defined by the subclass of this task. + * + * @see #onPreExecute() + * @see #onPostExecute + * @see #publishProgress + */ + protected abstract Result doInBackground(Params... params); + + /** + * Runs on the UI thread before {@link #doInBackground}. + * + * @see #onPostExecute + * @see #doInBackground + */ + protected void onPreExecute() { + } + + /** + *

Runs on the UI thread after {@link #doInBackground}. The + * specified result is the value returned by {@link #doInBackground}.

+ * + *

This method won't be invoked if the task was cancelled.

+ * + * @param result The result of the operation computed by {@link #doInBackground}. + * + * @see #onPreExecute + * @see #doInBackground + * @see #onCancelled(Object) + */ + @SuppressWarnings({"UnusedDeclaration"}) + protected void onPostExecute(Result result) { + } + + /** + * Runs on the UI thread after {@link #publishProgress} is invoked. + * The specified values are the values passed to {@link #publishProgress}. + * + * @param values The values indicating progress. + * + * @see #publishProgress + * @see #doInBackground + */ + @SuppressWarnings({"UnusedDeclaration"}) + protected void onProgressUpdate(Progress... values) { + } + + /** + *

Runs on the UI thread after {@link #cancel(boolean)} is invoked and + * {@link #doInBackground(Object[])} has finished.

+ * + *

The default implementation simply invokes {@link #onCancelled()} and + * ignores the result. If you write your own implementation, do not call + * super.onCancelled(result).

+ * + * @param result The result, if any, computed in + * {@link #doInBackground(Object[])}, can be null + * + * @see #cancel(boolean) + * @see #isCancelled() + */ + @SuppressWarnings({"UnusedParameters"}) + protected void onCancelled(Result result) { + onCancelled(); + } + + /** + *

Applications should preferably override {@link #onCancelled(Object)}. + * This method is invoked by the default implementation of + * {@link #onCancelled(Object)}.

+ * + *

Runs on the UI thread after {@link #cancel(boolean)} is invoked and + * {@link #doInBackground(Object[])} has finished.

+ * + * @see #onCancelled(Object) + * @see #cancel(boolean) + * @see #isCancelled() + */ + protected void onCancelled() { + } + + /** + * Returns true if this task was cancelled before it completed + * normally. If you are calling {@link #cancel(boolean)} on the task, + * the value returned by this method should be checked periodically from + * {@link #doInBackground(Object[])} to end the task as soon as possible. + * + * @return true if task was cancelled before it completed + * + * @see #cancel(boolean) + */ + public final boolean isCancelled() { + return mCancelled.get(); + } + + /** + *

Attempts to cancel execution of this task. This attempt will + * fail if the task has already completed, already been cancelled, + * or could not be cancelled for some other reason. If successful, + * and this task has not started when cancel is called, + * this task should never run. If the task has already started, + * then the mayInterruptIfRunning parameter determines + * whether the thread executing this task should be interrupted in + * an attempt to stop the task.

+ * + *

Calling this method will result in {@link #onCancelled(Object)} being + * invoked on the UI thread after {@link #doInBackground(Object[])} + * returns. Calling this method guarantees that {@link #onPostExecute(Object)} + * is never invoked. After invoking this method, you should check the + * value returned by {@link #isCancelled()} periodically from + * {@link #doInBackground(Object[])} to finish the task as early as + * possible.

+ * + * @param mayInterruptIfRunning true if the thread executing this + * task should be interrupted; otherwise, in-progress tasks are allowed + * to complete. + * + * @return false if the task could not be cancelled, + * typically because it has already completed normally; + * true otherwise + * + * @see #isCancelled() + * @see #onCancelled(Object) + */ + public final boolean cancel(boolean mayInterruptIfRunning) { + mCancelled.set(true); + return mFuture.cancel(mayInterruptIfRunning); + } + + /** + * Waits if necessary for the computation to complete, and then + * retrieves its result. + * + * @return The computed result. + * + * @throws CancellationException If the computation was cancelled. + * @throws ExecutionException If the computation threw an exception. + * @throws InterruptedException If the current thread was interrupted + * while waiting. + */ + public final Result get() throws InterruptedException, ExecutionException { + return mFuture.get(); + } + + /** + * Waits if necessary for at most the given time for the computation + * to complete, and then retrieves its result. + * + * @param timeout Time to wait before cancelling the operation. + * @param unit The time unit for the timeout. + * + * @return The computed result. + * + * @throws CancellationException If the computation was cancelled. + * @throws ExecutionException If the computation threw an exception. + * @throws InterruptedException If the current thread was interrupted + * while waiting. + * @throws TimeoutException If the wait timed out. + */ + public final Result get(long timeout, TimeUnit unit) throws InterruptedException, + ExecutionException, TimeoutException { + return mFuture.get(timeout, unit); + } + + /** + * Executes the task with the specified parameters. The task returns + * itself (this) so that the caller can keep a reference to it. + * + *

Note: this function schedules the task on a queue for a single background + * thread or pool of threads depending on the platform version. When first + * introduced, AsyncTasks were executed serially on a single background thread. + * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed + * to a pool of threads allowing multiple tasks to operate in parallel. Starting + * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being + * executed on a single thread to avoid common application errors caused + * by parallel execution. If you truly want parallel execution, you can use + * the {@link #executeOnExecutor} version of this method + * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings + * on its use. + * + *

This method must be invoked on the UI thread. + * + * @param params The parameters of the task. + * + * @return This instance of AsyncTask. + * + * @throws IllegalStateException If {@link #getStatus()} returns either + * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. + * + * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) + * @see #execute(Runnable) + */ + public final AsyncTask execute(Params... params) { + return executeOnExecutor(sDefaultExecutor, params); + } + + /** + * Executes the task with the specified parameters. The task returns + * itself (this) so that the caller can keep a reference to it. + * + *

This method is typically used with {@link #THREAD_POOL_EXECUTOR} to + * allow multiple tasks to run in parallel on a pool of threads managed by + * AsyncTask, however you can also use your own {@link Executor} for custom + * behavior. + * + *

Warning: Allowing multiple tasks to run in parallel from + * a thread pool is generally not what one wants, because the order + * of their operation is not defined. For example, if these tasks are used + * to modify any state in common (such as writing a file due to a button click), + * there are no guarantees on the order of the modifications. + * Without careful work it is possible in rare cases for the newer version + * of the data to be over-written by an older one, leading to obscure data + * loss and stability issues. Such changes are best + * executed in serial; to guarantee such work is serialized regardless of + * platform version you can use this function with {@link #SERIAL_EXECUTOR}. + * + *

This method must be invoked on the UI thread. + * + * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a + * convenient process-wide thread pool for tasks that are loosely coupled. + * @param params The parameters of the task. + * + * @return This instance of AsyncTask. + * + * @throws IllegalStateException If {@link #getStatus()} returns either + * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. + * + * @see #execute(Object[]) + */ + public final AsyncTask executeOnExecutor(Executor exec, + Params... params) { + if (mStatus != Status.PENDING) { + switch (mStatus) { + case RUNNING: + throw new IllegalStateException("Cannot execute task:" + + " the task is already running."); + case FINISHED: + throw new IllegalStateException("Cannot execute task:" + + " the task has already been executed " + + "(a task can be executed only once)"); + } + } + + mStatus = Status.RUNNING; + + onPreExecute(); + + mWorker.mParams = params; + exec.execute(mFuture); + + return this; + } + + /** + * Convenience version of {@link #execute(Object...)} for use with + * a simple Runnable object. See {@link #execute(Object[])} for more + * information on the order of execution. + * + * @see #execute(Object[]) + * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) + */ + public static void execute(Runnable runnable) { + sDefaultExecutor.execute(runnable); + } + + /** + * This method can be invoked from {@link #doInBackground} to + * publish updates on the UI thread while the background computation is + * still running. Each call to this method will trigger the execution of + * {@link #onProgressUpdate} on the UI thread. + * + * {@link #onProgressUpdate} will note be called if the task has been + * canceled. + * + * @param values The progress values to update the UI with. + * + * @see #onProgressUpdate + * @see #doInBackground + */ + protected final void publishProgress(Progress... values) { + if (!isCancelled()) { + sHandler.obtainMessage(MESSAGE_POST_PROGRESS, + new AsyncTaskResult(this, values)).sendToTarget(); + } + } + + private void finish(Result result) { + if (isCancelled()) { + onCancelled(result); + } else { + onPostExecute(result); + } + mStatus = Status.FINISHED; + } + + private static class InternalHandler extends Handler { + @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) + @Override + public void handleMessage(Message msg) { + AsyncTaskResult result = (AsyncTaskResult) msg.obj; + switch (msg.what) { + case MESSAGE_POST_RESULT: + // There is only one result + result.mTask.finish(result.mData[0]); + break; + case MESSAGE_POST_PROGRESS: + result.mTask.onProgressUpdate(result.mData); + break; + } + } + } + + private static abstract class WorkerRunnable implements Callable { + Params[] mParams; + } + + @SuppressWarnings({"RawUseOfParameterizedType"}) + private static class AsyncTaskResult { + final AsyncTask mTask; + final Data[] mData; + + AsyncTaskResult(AsyncTask task, Data... data) { + mTask = task; + mData = data; + } + } +} diff --git a/android/src/com/artifex/mupdfdemo/BitmapHolder.java b/android/src/com/artifex/mupdfdemo/BitmapHolder.java new file mode 100644 index 00000000..f563e897 --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/BitmapHolder.java @@ -0,0 +1,21 @@ +package com.artifex.mupdfdemo; + +import android.graphics.Bitmap; + +public class BitmapHolder { + private Bitmap bm; + + public BitmapHolder() { + bm = null; + } + + public synchronized void setBm(Bitmap abm) { + if (bm != null && bm != abm) + bm.recycle(); + bm = abm; + } + + public synchronized Bitmap getBm() { + return bm; + } +} diff --git a/android/src/com/artifex/mupdfdemo/ChoosePDFActivity.java b/android/src/com/artifex/mupdfdemo/ChoosePDFActivity.java new file mode 100644 index 00000000..57e9893b --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/ChoosePDFActivity.java @@ -0,0 +1,179 @@ +package com.artifex.mupdfdemo; + +import java.io.File; +import java.io.FileFilter; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.FileObserver; +import android.os.Handler; +import android.view.View; +import android.widget.ListView; + +public class ChoosePDFActivity extends ListActivity { + static private File mDirectory; + static private Map mPositions = new HashMap(); + private File mParent; + private File [] mDirs; + private File [] mFiles; + private Handler mHandler; + private Runnable mUpdateFiles; + private ChoosePDFAdapter adapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Resources res = getResources(); + String appName = res.getString(R.string.app_name); + String version = res.getString(R.string.version); + String title = res.getString(R.string.picker_title); + setTitle(String.format(title, appName, version)); + + String storageState = Environment.getExternalStorageState(); + + if (!Environment.MEDIA_MOUNTED.equals(storageState) + && !Environment.MEDIA_MOUNTED_READ_ONLY.equals(storageState)) + { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.no_media_warning); + builder.setMessage(R.string.no_media_hint); + AlertDialog alert = builder.create(); + alert.setButton(AlertDialog.BUTTON_POSITIVE,"Dismiss", + new OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + alert.show(); + return; + } + + if (mDirectory == null) + mDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + + // Create a list adapter... + adapter = new ChoosePDFAdapter(getLayoutInflater()); + setListAdapter(adapter); + + // ...that is updated dynamically when files are scanned + mHandler = new Handler(); + mUpdateFiles = new Runnable() { + public void run() { + mParent = mDirectory.getParentFile(); + + mDirs = mDirectory.listFiles(new FileFilter() { + + public boolean accept(File file) { + return file.isDirectory(); + } + }); + if (mDirs == null) + mDirs = new File[0]; + + mFiles = mDirectory.listFiles(new FileFilter() { + + public boolean accept(File file) { + if (file.isDirectory()) + return false; + String fname = file.getName().toLowerCase(); + if (fname.endsWith(".pdf")) + return true; + if (fname.endsWith(".xps")) + return true; + if (fname.endsWith(".cbz")) + return true; + return false; + } + }); + if (mFiles == null) + mFiles = new File[0]; + + Arrays.sort(mFiles, new Comparator() { + public int compare(File arg0, File arg1) { + return arg0.getName().compareToIgnoreCase(arg1.getName()); + } + }); + + Arrays.sort(mDirs, new Comparator() { + public int compare(File arg0, File arg1) { + return arg0.getName().compareToIgnoreCase(arg1.getName()); + } + }); + + adapter.clear(); + if (mParent != null) + adapter.add(new ChoosePDFItem(ChoosePDFItem.Type.PARENT, "..")); + for (File f : mDirs) + adapter.add(new ChoosePDFItem(ChoosePDFItem.Type.DIR, f.getName())); + for (File f : mFiles) + adapter.add(new ChoosePDFItem(ChoosePDFItem.Type.DOC, f.getName())); + + lastPosition(); + } + }; + + // Start initial file scan... + mHandler.post(mUpdateFiles); + + // ...and observe the directory and scan files upon changes. + FileObserver observer = new FileObserver(mDirectory.getPath(), FileObserver.CREATE | FileObserver.DELETE) { + public void onEvent(int event, String path) { + mHandler.post(mUpdateFiles); + } + }; + observer.startWatching(); + } + + private void lastPosition() { + String p = mDirectory.getAbsolutePath(); + if (mPositions.containsKey(p)) + getListView().setSelection(mPositions.get(p)); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + super.onListItemClick(l, v, position, id); + + mPositions.put(mDirectory.getAbsolutePath(), getListView().getFirstVisiblePosition()); + + if (position < (mParent == null ? 0 : 1)) { + mDirectory = mParent; + mHandler.post(mUpdateFiles); + return; + } + + position -= (mParent == null ? 0 : 1); + + if (position < mDirs.length) { + mDirectory = mDirs[position]; + mHandler.post(mUpdateFiles); + return; + } + + position -= mDirs.length; + + Uri uri = Uri.parse(mFiles[position].getAbsolutePath()); + Intent intent = new Intent(this,MuPDFActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(uri); + startActivity(intent); + } + + @Override + protected void onPause() { + super.onPause(); + mPositions.put(mDirectory.getAbsolutePath(), getListView().getFirstVisiblePosition()); + } +} diff --git a/android/src/com/artifex/mupdfdemo/ChoosePDFAdapter.java b/android/src/com/artifex/mupdfdemo/ChoosePDFAdapter.java new file mode 100644 index 00000000..c26174a2 --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/ChoosePDFAdapter.java @@ -0,0 +1,64 @@ +package com.artifex.mupdfdemo; + +import java.util.LinkedList; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +public class ChoosePDFAdapter extends BaseAdapter { + private final LinkedList mItems; + private final LayoutInflater mInflater; + + public ChoosePDFAdapter(LayoutInflater inflater) { + mInflater = inflater; + mItems = new LinkedList(); + } + + public void clear() { + mItems.clear(); + } + + public void add(ChoosePDFItem item) { + mItems.add(item); + notifyDataSetChanged(); + } + + public int getCount() { + return mItems.size(); + } + + public Object getItem(int i) { + return null; + } + + public long getItemId(int arg0) { + return 0; + } + + private int iconForType(ChoosePDFItem.Type type) { + switch (type) { + case PARENT: return R.drawable.ic_arrow_up; + case DIR: return R.drawable.ic_dir; + case DOC: return R.drawable.ic_doc; + default: return 0; + } + } + + public View getView(int position, View convertView, ViewGroup parent) { + View v; + if (convertView == null) { + v = mInflater.inflate(R.layout.picker_entry, null); + } else { + v = convertView; + } + ChoosePDFItem item = mItems.get(position); + ((TextView)v.findViewById(R.id.name)).setText(item.name); + ((ImageView)v.findViewById(R.id.icon)).setBackgroundResource(iconForType(item.type)); + return v; + } + +} diff --git a/android/src/com/artifex/mupdfdemo/ChoosePDFItem.java b/android/src/com/artifex/mupdfdemo/ChoosePDFItem.java new file mode 100644 index 00000000..de6e1d52 --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/ChoosePDFItem.java @@ -0,0 +1,15 @@ +package com.artifex.mupdfdemo; + +public class ChoosePDFItem { + enum Type { + PARENT, DIR, DOC + } + + final public Type type; + final public String name; + + public ChoosePDFItem (Type t, String n) { + type = t; + name = n; + } +} diff --git a/android/src/com/artifex/mupdfdemo/LinkInfo.java b/android/src/com/artifex/mupdfdemo/LinkInfo.java new file mode 100644 index 00000000..5aeaccbe --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/LinkInfo.java @@ -0,0 +1,14 @@ +package com.artifex.mupdfdemo; + +import android.graphics.RectF; + +public class LinkInfo { + final public RectF rect; + + public LinkInfo(float l, float t, float r, float b) { + rect = new RectF(l, t, r, b); + } + + public void acceptVisitor(LinkInfoVisitor visitor) { + } +} diff --git a/android/src/com/artifex/mupdfdemo/LinkInfoExternal.java b/android/src/com/artifex/mupdfdemo/LinkInfoExternal.java new file mode 100644 index 00000000..574b6264 --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/LinkInfoExternal.java @@ -0,0 +1,14 @@ +package com.artifex.mupdfdemo; + +public class LinkInfoExternal extends LinkInfo { + final public String url; + + public LinkInfoExternal(float l, float t, float r, float b, String u) { + super(l, t, r, b); + url = u; + } + + public void acceptVisitor(LinkInfoVisitor visitor) { + visitor.visitExternal(this); + } +} diff --git a/android/src/com/artifex/mupdfdemo/LinkInfoInternal.java b/android/src/com/artifex/mupdfdemo/LinkInfoInternal.java new file mode 100644 index 00000000..761bf87a --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/LinkInfoInternal.java @@ -0,0 +1,14 @@ +package com.artifex.mupdfdemo; + +public class LinkInfoInternal extends LinkInfo { + final public int pageNumber; + + public LinkInfoInternal(float l, float t, float r, float b, int p) { + super(l, t, r, b); + pageNumber = p; + } + + public void acceptVisitor(LinkInfoVisitor visitor) { + visitor.visitInternal(this); + } +} diff --git a/android/src/com/artifex/mupdfdemo/LinkInfoRemote.java b/android/src/com/artifex/mupdfdemo/LinkInfoRemote.java new file mode 100644 index 00000000..731e6408 --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/LinkInfoRemote.java @@ -0,0 +1,18 @@ +package com.artifex.mupdfdemo; + +public class LinkInfoRemote extends LinkInfo { + final public String fileSpec; + final public int pageNumber; + final public boolean newWindow; + + public LinkInfoRemote(float l, float t, float r, float b, String f, int p, boolean n) { + super(l, t, r, b); + fileSpec = f; + pageNumber = p; + newWindow = n; + } + + public void acceptVisitor(LinkInfoVisitor visitor) { + visitor.visitRemote(this); + } +} diff --git a/android/src/com/artifex/mupdfdemo/LinkInfoVisitor.java b/android/src/com/artifex/mupdfdemo/LinkInfoVisitor.java new file mode 100644 index 00000000..ecd093e4 --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/LinkInfoVisitor.java @@ -0,0 +1,7 @@ +package com.artifex.mupdfdemo; + +abstract public class LinkInfoVisitor { + public abstract void visitInternal(LinkInfoInternal li); + public abstract void visitExternal(LinkInfoExternal li); + public abstract void visitRemote(LinkInfoRemote li); +} diff --git a/android/src/com/artifex/mupdfdemo/MuPDFActivity.java b/android/src/com/artifex/mupdfdemo/MuPDFActivity.java new file mode 100644 index 00000000..25cb68e9 --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/MuPDFActivity.java @@ -0,0 +1,1031 @@ +package com.artifex.mupdfdemo; + +import java.util.concurrent.Executor; + +import android.animation.Animator; +import android.animation.AnimatorInflater; +import android.animation.AnimatorSet; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.graphics.Color; +import android.graphics.RectF; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.text.Editable; +import android.text.TextWatcher; +import android.text.method.PasswordTransformationMethod; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.TranslateAnimation; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.RelativeLayout; +import android.widget.SeekBar; +import android.widget.TextView; +import android.widget.ViewAnimator; + +class ThreadPerTaskExecutor implements Executor { + public void execute(Runnable r) { + new Thread(r).start(); + } +} + +class SearchTaskResult { + public final String txt; + public final int pageNumber; + public final RectF searchBoxes[]; + static private SearchTaskResult singleton; + + SearchTaskResult(String _txt, int _pageNumber, RectF _searchBoxes[]) { + txt = _txt; + pageNumber = _pageNumber; + searchBoxes = _searchBoxes; + } + + static public SearchTaskResult get() { + return singleton; + } + + static public void set(SearchTaskResult r) { + singleton = r; + } +} + +class ProgressDialogX extends ProgressDialog { + public ProgressDialogX(Context context) { + super(context); + } + + private boolean mCancelled = false; + + public boolean isCancelled() { + return mCancelled; + } + + @Override + public void cancel() { + mCancelled = true; + super.cancel(); + } +} + +public class MuPDFActivity extends Activity +{ + /* The core rendering instance */ + private final int TAP_PAGE_MARGIN = 5; + private static final int SEARCH_PROGRESS_DELAY = 200; + private MuPDFCore core; + private String mFileName; + private ReaderView mDocView; + private View mButtonsView; + private boolean mButtonsVisible; + private EditText mPasswordView; + private TextView mFilenameView; + private SeekBar mPageSlider; + private int mPageSliderRes; + private TextView mPageNumberView; + private TextView mInfoView; + private ImageButton mSearchButton; + private ImageButton mSelectButton; + private ImageButton mCancelSelectButton; + private ImageButton mCopySelectButton; + private ImageButton mCancelButton; + private ImageButton mOutlineButton; + private ViewAnimator mTopBarSwitcher; + private ImageButton mLinkButton; + private boolean mTopBarIsSearch; + private ImageButton mSearchBack; + private ImageButton mSearchFwd; + private EditText mSearchText; + private AsyncTask mSearchTask; + //private SearchTaskResult mSearchTaskResult; + private AlertDialog.Builder mAlertBuilder; + private boolean mLinkHighlight = false; + private boolean mSelecting = false; + private final Handler mHandler = new Handler(); + private boolean mAlertsActive= false; + private AsyncTask mAlertTask; + private AlertDialog mAlertDialog; + + public void createAlertWaiter() { + mAlertsActive = true; + // All mupdf library calls are performed on asynchronous tasks to avoid stalling + // the UI. Some calls can lead to javascript-invoked requests to display an + // alert dialog and collect a reply from the user. The task has to be blocked + // until the user's reply is received. This method creates an asynchronous task, + // the purpose of which is to wait of these requests and produce the dialog + // in response, while leaving the core blocked. When the dialog receives the + // user's response, it is sent to the core via replyToAlert, unblocking it. + // Another alert-waiting task is then created to pick up the next alert. + if (mAlertTask != null) { + mAlertTask.cancel(true); + mAlertTask = null; + } + if (mAlertDialog != null) { + mAlertDialog.cancel(); + mAlertDialog = null; + } + mAlertTask = new AsyncTask() { + + @Override + protected MuPDFAlert doInBackground(Void... arg0) { + if (!mAlertsActive) + return null; + + return core.waitForAlert(); + } + + @Override + protected void onPostExecute(final MuPDFAlert result) { + // core.waitForAlert may return null when shutting down + if (result == null) + return; + final MuPDFAlert.ButtonPressed pressed[] = new MuPDFAlert.ButtonPressed[3]; + for(int i = 0; i < 3; i++) + pressed[i] = MuPDFAlert.ButtonPressed.None; + DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mAlertDialog = null; + if (mAlertsActive) { + int index = 0; + switch (which) { + case AlertDialog.BUTTON1: index=0; break; + case AlertDialog.BUTTON2: index=1; break; + case AlertDialog.BUTTON3: index=2; break; + } + result.buttonPressed = pressed[index]; + // Send the user's response to the core, so that it can + // continue processing. + core.replyToAlert(result); + // Create another alert-waiter to pick up the next alert. + createAlertWaiter(); + } + } + }; + mAlertDialog = mAlertBuilder.create(); + mAlertDialog.setTitle(result.title); + mAlertDialog.setMessage(result.message); + switch (result.iconType) + { + case Error: + break; + case Warning: + break; + case Question: + break; + case Status: + break; + } + switch (result.buttonGroupType) + { + case OkCancel: + mAlertDialog.setButton(AlertDialog.BUTTON2, "Cancel", listener); + pressed[1] = MuPDFAlert.ButtonPressed.Cancel; + case Ok: + mAlertDialog.setButton(AlertDialog.BUTTON1, "Ok", listener); + pressed[0] = MuPDFAlert.ButtonPressed.Ok; + break; + case YesNoCancel: + mAlertDialog.setButton(AlertDialog.BUTTON3, "Cancel", listener); + pressed[2] = MuPDFAlert.ButtonPressed.Cancel; + case YesNo: + mAlertDialog.setButton(AlertDialog.BUTTON1, "Yes", listener); + pressed[0] = MuPDFAlert.ButtonPressed.Yes; + mAlertDialog.setButton(AlertDialog.BUTTON2, "No", listener); + pressed[1] = MuPDFAlert.ButtonPressed.No; + break; + } + mAlertDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + mAlertDialog = null; + if (mAlertsActive) { + result.buttonPressed = MuPDFAlert.ButtonPressed.None; + core.replyToAlert(result); + createAlertWaiter(); + } + } + }); + + mAlertDialog.show(); + } + }; + + mAlertTask.executeOnExecutor(new ThreadPerTaskExecutor()); + } + + public void destroyAlertWaiter() { + mAlertsActive = false; + if (mAlertDialog != null) { + mAlertDialog.cancel(); + mAlertDialog = null; + } + if (mAlertTask != null) { + mAlertTask.cancel(true); + mAlertTask = null; + } + } + + private MuPDFCore openFile(String path) + { + int lastSlashPos = path.lastIndexOf('/'); + mFileName = new String(lastSlashPos == -1 + ? path + : path.substring(lastSlashPos+1)); + System.out.println("Trying to open "+path); + try + { + core = new MuPDFCore(path); + // New file: drop the old outline data + OutlineActivityData.set(null); + } + catch (Exception e) + { + System.out.println(e); + return null; + } + return core; + } + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + mAlertBuilder = new AlertDialog.Builder(this); + + if (core == null) { + core = (MuPDFCore)getLastNonConfigurationInstance(); + + if (savedInstanceState != null && savedInstanceState.containsKey("FileName")) { + mFileName = savedInstanceState.getString("FileName"); + } + } + if (core == null) { + Intent intent = getIntent(); + if (Intent.ACTION_VIEW.equals(intent.getAction())) { + Uri uri = intent.getData(); + if (uri.toString().startsWith("content://")) { + // Handle view requests from the Transformer Prime's file manager + // Hopefully other file managers will use this same scheme, if not + // using explicit paths. + Cursor cursor = getContentResolver().query(uri, new String[]{"_data"}, null, null, null); + if (cursor.moveToFirst()) { + uri = Uri.parse(cursor.getString(0)); + } + } + core = openFile(Uri.decode(uri.getEncodedPath())); + SearchTaskResult.set(null); + } + if (core != null && core.needsPassword()) { + requestPassword(savedInstanceState); + return; + } + } + if (core == null) + { + AlertDialog alert = mAlertBuilder.create(); + alert.setTitle(R.string.open_failed); + alert.setButton(AlertDialog.BUTTON_POSITIVE, "Dismiss", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + alert.show(); + return; + } + + createUI(savedInstanceState); + } + + public void requestPassword(final Bundle savedInstanceState) { + mPasswordView = new EditText(this); + mPasswordView.setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); + mPasswordView.setTransformationMethod(new PasswordTransformationMethod()); + + AlertDialog alert = mAlertBuilder.create(); + alert.setTitle(R.string.enter_password); + alert.setView(mPasswordView); + alert.setButton(AlertDialog.BUTTON_POSITIVE, "Ok", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (core.authenticatePassword(mPasswordView.getText().toString())) { + createUI(savedInstanceState); + } else { + requestPassword(savedInstanceState); + } + } + }); + alert.setButton(AlertDialog.BUTTON_NEGATIVE, "Cancel", + new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + alert.show(); + } + + public void createUI(Bundle savedInstanceState) { + if (core == null) + return; + + // Now create the UI. + // First create the document view making use of the ReaderView's internal + // gesture recognition + mDocView = new ReaderView(this) { + private boolean showButtonsDisabled; + + public boolean onSingleTapUp(MotionEvent e) { + LinkInfo link = null; + + if (!mSelecting && !showButtonsDisabled) { + MuPDFPageView pageView = (MuPDFPageView) mDocView.getDisplayedView(); + if (MuPDFCore.javascriptSupported() && pageView.passClickEvent(e.getX(), e.getY())) { + // If the page consumes the event do nothing else + } else if (mLinkHighlight && pageView != null && (link = pageView.hitLink(e.getX(), e.getY())) != null) { + link.acceptVisitor(new LinkInfoVisitor() { + @Override + public void visitInternal(LinkInfoInternal li) { + // Clicked on an internal (GoTo) link + mDocView.setDisplayedViewIndex(li.pageNumber); + } + + @Override + public void visitExternal(LinkInfoExternal li) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(li.url)); + startActivity(intent); + } + + @Override + public void visitRemote(LinkInfoRemote li) { + // Clicked on a remote (GoToR) link + } + }); + } else if (e.getX() < super.getWidth()/TAP_PAGE_MARGIN) { + super.moveToPrevious(); + } else if (e.getX() > super.getWidth()*(TAP_PAGE_MARGIN-1)/TAP_PAGE_MARGIN) { + super.moveToNext(); + } else if (!mButtonsVisible) { + showButtons(); + } else { + hideButtons(); + } + } + return super.onSingleTapUp(e); + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + if (!mSelecting) { + if (!showButtonsDisabled) + hideButtons(); + + return super.onScroll(e1, e2, distanceX, distanceY); + } else { + MuPDFPageView pageView = (MuPDFPageView) mDocView.getDisplayedView(); + if (pageView != null) + pageView.selectText(e1.getX(), e1.getY(), e2.getX(), e2.getY()); + return true; + } + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, + float velocityX, float velocityY) { + if (!mSelecting) + return super.onFling(e1, e2, velocityX, velocityY); + else + return true; + } + + public boolean onScaleBegin(ScaleGestureDetector d) { + // Disabled showing the buttons until next touch. + // Not sure why this is needed, but without it + // pinch zoom can make the buttons appear + showButtonsDisabled = true; + return super.onScaleBegin(d); + } + + public boolean onTouchEvent(MotionEvent event) { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) + showButtonsDisabled = false; + + return super.onTouchEvent(event); + } + + protected void onChildSetup(int i, View v) { + if (SearchTaskResult.get() != null && SearchTaskResult.get().pageNumber == i) + ((PageView)v).setSearchBoxes(SearchTaskResult.get().searchBoxes); + else + ((PageView)v).setSearchBoxes(null); + + ((PageView)v).setLinkHighlighting(mLinkHighlight); + + ((MuPDFPageView)v).setChangeReporter(new Runnable() { + public void run() { + mDocView.applyToChildren(new ReaderView.ViewMapper() { + @Override + void applyToView(View view) { + ((MuPDFPageView)view).update(); + } + }); + } + }); + } + + protected void onMoveToChild(int i) { + if (core == null) + return; + mPageNumberView.setText(String.format("%d / %d", i+1, core.countPages())); + mPageSlider.setMax((core.countPages()-1) * mPageSliderRes); + mPageSlider.setProgress(i * mPageSliderRes); + if (SearchTaskResult.get() != null && SearchTaskResult.get().pageNumber != i) { + SearchTaskResult.set(null); + mDocView.resetupChildren(); + } + } + + protected void onSettle(View v) { + // When the layout has settled ask the page to render + // in HQ + ((PageView)v).addHq(false); + } + + protected void onUnsettle(View v) { + // When something changes making the previous settled view + // no longer appropriate, tell the page to remove HQ + ((PageView)v).removeHq(); + } + + @Override + protected void onNotInUse(View v) { + ((PageView)v).releaseResources(); + } + }; + mDocView.setAdapter(new MuPDFPageAdapter(this, core)); + + // Make the buttons overlay, and store all its + // controls in variables + makeButtonsView(); + + // Set up the page slider + int smax = Math.max(core.countPages()-1,1); + mPageSliderRes = ((10 + smax - 1)/smax) * 2; + + // Set the file-name text + mFilenameView.setText(mFileName); + + // Activate the seekbar + mPageSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + public void onStopTrackingTouch(SeekBar seekBar) { + mDocView.setDisplayedViewIndex((seekBar.getProgress()+mPageSliderRes/2)/mPageSliderRes); + } + + public void onStartTrackingTouch(SeekBar seekBar) {} + + public void onProgressChanged(SeekBar seekBar, int progress, + boolean fromUser) { + updatePageNumView((progress+mPageSliderRes/2)/mPageSliderRes); + } + }); + + // Activate the search-preparing button + mSearchButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + searchModeOn(); + } + }); + + // Activate the select button + mSelectButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + mSelecting = true; + mTopBarSwitcher.setDisplayedChild(2); + } + }); + + mCancelSelectButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + PageView pageView = (PageView) mDocView.getDisplayedView(); + if (pageView != null) + pageView.deselectText(); + mSelecting = false; + mTopBarSwitcher.setDisplayedChild(0); + } + }); + + final Context context = this; + mCopySelectButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + PageView pageView = (PageView) mDocView.getDisplayedView(); + boolean copied = false; + if (pageView != null) + copied = pageView.copySelection(); + mSelecting = false; + mTopBarSwitcher.setDisplayedChild(0); + mInfoView.setText(copied?"Copied to clipboard":"No text selected"); + AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(context, R.animator.info); + set.setTarget(mInfoView); + set.addListener(new Animator.AnimatorListener() { + public void onAnimationStart(Animator animation) { + mInfoView.setVisibility(View.VISIBLE); + } + + public void onAnimationRepeat(Animator animation) { + } + + public void onAnimationEnd(Animator animation) { + mInfoView.setVisibility(View.INVISIBLE); + } + + public void onAnimationCancel(Animator animation) { + } + }); + set.start(); + } + }); + + mCancelButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + searchModeOff(); + } + }); + + // Search invoking buttons are disabled while there is no text specified + mSearchBack.setEnabled(false); + mSearchFwd.setEnabled(false); + mSearchBack.setColorFilter(Color.argb(255, 128, 128, 128)); + mSearchFwd.setColorFilter(Color.argb(255, 128, 128, 128)); + + // React to interaction with the text widget + mSearchText.addTextChangedListener(new TextWatcher() { + + public void afterTextChanged(Editable s) { + boolean haveText = s.toString().length() > 0; + mSearchBack.setEnabled(haveText); + mSearchFwd.setEnabled(haveText); + if (haveText) { + mSearchBack.setColorFilter(Color.argb(255, 255, 255, 255)); + mSearchFwd.setColorFilter(Color.argb(255, 255, 255, 255)); + } else { + mSearchBack.setColorFilter(Color.argb(255, 128, 128, 128)); + mSearchFwd.setColorFilter(Color.argb(255, 128, 128, 128)); + } + + // Remove any previous search results + if (SearchTaskResult.get() != null && !mSearchText.getText().toString().equals(SearchTaskResult.get().txt)) { + SearchTaskResult.set(null); + mDocView.resetupChildren(); + } + } + public void beforeTextChanged(CharSequence s, int start, int count, + int after) {} + public void onTextChanged(CharSequence s, int start, int before, + int count) {} + }); + + //React to Done button on keyboard + mSearchText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) + search(1); + return false; + } + }); + + mSearchText.setOnKeyListener(new View.OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) + search(1); + return false; + } + }); + + // Activate search invoking buttons + mSearchBack.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + search(-1); + } + }); + mSearchFwd.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + search(1); + } + }); + + mLinkButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + if (mLinkHighlight) { + mLinkButton.setColorFilter(Color.argb(0xFF, 255, 255, 255)); + mLinkHighlight = false; + } else { + // LINK_COLOR tint + mLinkButton.setColorFilter(Color.argb(0xFF, 172, 114, 37)); + mLinkHighlight = true; + } + // Inform pages of the change. + mDocView.resetupChildren(); + } + }); + + if (core.hasOutline()) { + mOutlineButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + OutlineItem outline[] = core.getOutline(); + if (outline != null) { + OutlineActivityData.get().items = outline; + Intent intent = new Intent(MuPDFActivity.this, OutlineActivity.class); + startActivityForResult(intent, 0); + } + } + }); + } else { + mOutlineButton.setVisibility(View.GONE); + } + + // Reenstate last state if it was recorded + SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); + mDocView.setDisplayedViewIndex(prefs.getInt("page"+mFileName, 0)); + + if (savedInstanceState == null || !savedInstanceState.getBoolean("ButtonsHidden", false)) + showButtons(); + + if(savedInstanceState != null && savedInstanceState.getBoolean("SearchMode", false)) + searchModeOn(); + + // Stick the document view and the buttons overlay into a parent view + RelativeLayout layout = new RelativeLayout(this); + layout.addView(mDocView); + layout.addView(mButtonsView); + layout.setBackgroundResource(R.drawable.tiled_background); + //layout.setBackgroundResource(R.color.canvas); + setContentView(layout); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode >= 0) + mDocView.setDisplayedViewIndex(resultCode); + super.onActivityResult(requestCode, resultCode, data); + } + + public Object onRetainNonConfigurationInstance() + { + MuPDFCore mycore = core; + core = null; + return mycore; + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + if (mFileName != null && mDocView != null) { + outState.putString("FileName", mFileName); + + // Store current page in the prefs against the file name, + // so that we can pick it up each time the file is loaded + // Other info is needed only for screen-orientation change, + // so it can go in the bundle + SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); + SharedPreferences.Editor edit = prefs.edit(); + edit.putInt("page"+mFileName, mDocView.getDisplayedViewIndex()); + edit.commit(); + } + + if (!mButtonsVisible) + outState.putBoolean("ButtonsHidden", true); + + if (mTopBarIsSearch) + outState.putBoolean("SearchMode", true); + } + + @Override + protected void onPause() { + super.onPause(); + + killSearch(); + + if (mFileName != null && mDocView != null) { + SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); + SharedPreferences.Editor edit = prefs.edit(); + edit.putInt("page"+mFileName, mDocView.getDisplayedViewIndex()); + edit.commit(); + } + } + + public void onDestroy() + { + if (core != null) + core.onDestroy(); + if (mAlertTask != null) { + mAlertTask.cancel(true); + mAlertTask = null; + } + core = null; + super.onDestroy(); + } + + void showButtons() { + if (core == null) + return; + if (!mButtonsVisible) { + mButtonsVisible = true; + // Update page number text and slider + int index = mDocView.getDisplayedViewIndex(); + updatePageNumView(index); + mPageSlider.setMax((core.countPages()-1)*mPageSliderRes); + mPageSlider.setProgress(index*mPageSliderRes); + if (mTopBarIsSearch) { + mSearchText.requestFocus(); + showKeyboard(); + } + + Animation anim = new TranslateAnimation(0, 0, -mTopBarSwitcher.getHeight(), 0); + anim.setDuration(200); + anim.setAnimationListener(new Animation.AnimationListener() { + public void onAnimationStart(Animation animation) { + mTopBarSwitcher.setVisibility(View.VISIBLE); + } + public void onAnimationRepeat(Animation animation) {} + public void onAnimationEnd(Animation animation) {} + }); + mTopBarSwitcher.startAnimation(anim); + + anim = new TranslateAnimation(0, 0, mPageSlider.getHeight(), 0); + anim.setDuration(200); + anim.setAnimationListener(new Animation.AnimationListener() { + public void onAnimationStart(Animation animation) { + mPageSlider.setVisibility(View.VISIBLE); + } + public void onAnimationRepeat(Animation animation) {} + public void onAnimationEnd(Animation animation) { + mPageNumberView.setVisibility(View.VISIBLE); + } + }); + mPageSlider.startAnimation(anim); + } + } + + void hideButtons() { + if (mButtonsVisible) { + mButtonsVisible = false; + hideKeyboard(); + + Animation anim = new TranslateAnimation(0, 0, 0, -mTopBarSwitcher.getHeight()); + anim.setDuration(200); + anim.setAnimationListener(new Animation.AnimationListener() { + public void onAnimationStart(Animation animation) {} + public void onAnimationRepeat(Animation animation) {} + public void onAnimationEnd(Animation animation) { + mTopBarSwitcher.setVisibility(View.INVISIBLE); + } + }); + mTopBarSwitcher.startAnimation(anim); + + anim = new TranslateAnimation(0, 0, 0, mPageSlider.getHeight()); + anim.setDuration(200); + anim.setAnimationListener(new Animation.AnimationListener() { + public void onAnimationStart(Animation animation) { + mPageNumberView.setVisibility(View.INVISIBLE); + } + public void onAnimationRepeat(Animation animation) {} + public void onAnimationEnd(Animation animation) { + mPageSlider.setVisibility(View.INVISIBLE); + } + }); + mPageSlider.startAnimation(anim); + } + } + + void searchModeOn() { + if (!mTopBarIsSearch) { + mTopBarIsSearch = true; + //Focus on EditTextWidget + mSearchText.requestFocus(); + showKeyboard(); + mTopBarSwitcher.setDisplayedChild(1); + } + } + + void searchModeOff() { + if (mTopBarIsSearch) { + mTopBarIsSearch = false; + hideKeyboard(); + mTopBarSwitcher.setDisplayedChild(0); + SearchTaskResult.set(null); + // Make the ReaderView act on the change to mSearchTaskResult + // via overridden onChildSetup method. + mDocView.resetupChildren(); + } + } + + void updatePageNumView(int index) { + if (core == null) + return; + mPageNumberView.setText(String.format("%d / %d", index+1, core.countPages())); + } + + void makeButtonsView() { + mButtonsView = getLayoutInflater().inflate(R.layout.buttons,null); + mFilenameView = (TextView)mButtonsView.findViewById(R.id.docNameText); + mPageSlider = (SeekBar)mButtonsView.findViewById(R.id.pageSlider); + mPageNumberView = (TextView)mButtonsView.findViewById(R.id.pageNumber); + mInfoView = (TextView)mButtonsView.findViewById(R.id.info); + mSearchButton = (ImageButton)mButtonsView.findViewById(R.id.searchButton); + mSelectButton = (ImageButton)mButtonsView.findViewById(R.id.selectButton); + mCancelSelectButton = (ImageButton)mButtonsView.findViewById(R.id.cancelSelectButton); + mCopySelectButton = (ImageButton)mButtonsView.findViewById(R.id.copySelectButton); + mCancelButton = (ImageButton)mButtonsView.findViewById(R.id.cancel); + mOutlineButton = (ImageButton)mButtonsView.findViewById(R.id.outlineButton); + mTopBarSwitcher = (ViewAnimator)mButtonsView.findViewById(R.id.switcher); + mSearchBack = (ImageButton)mButtonsView.findViewById(R.id.searchBack); + mSearchFwd = (ImageButton)mButtonsView.findViewById(R.id.searchForward); + mSearchText = (EditText)mButtonsView.findViewById(R.id.searchText); + mLinkButton = (ImageButton)mButtonsView.findViewById(R.id.linkButton); + mTopBarSwitcher.setVisibility(View.INVISIBLE); + mPageNumberView.setVisibility(View.INVISIBLE); + mInfoView.setVisibility(View.INVISIBLE); + mPageSlider.setVisibility(View.INVISIBLE); + } + + void showKeyboard() { + InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) + imm.showSoftInput(mSearchText, 0); + } + + void hideKeyboard() { + InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) + imm.hideSoftInputFromWindow(mSearchText.getWindowToken(), 0); + } + + void killSearch() { + if (mSearchTask != null) { + mSearchTask.cancel(true); + mSearchTask = null; + } + } + + void search(int direction) { + hideKeyboard(); + if (core == null) + return; + killSearch(); + + final int increment = direction; + final int startIndex = SearchTaskResult.get() == null ? mDocView.getDisplayedViewIndex() : SearchTaskResult.get().pageNumber + increment; + + final ProgressDialogX progressDialog = new ProgressDialogX(this); + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + progressDialog.setTitle(getString(R.string.searching_)); + progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + killSearch(); + } + }); + progressDialog.setMax(core.countPages()); + + mSearchTask = new AsyncTask() { + @Override + protected SearchTaskResult doInBackground(Void... params) { + int index = startIndex; + + while (0 <= index && index < core.countPages() && !isCancelled()) { + publishProgress(index); + RectF searchHits[] = core.searchPage(index, mSearchText.getText().toString()); + + if (searchHits != null && searchHits.length > 0) + return new SearchTaskResult(mSearchText.getText().toString(), index, searchHits); + + index += increment; + } + return null; + } + + @Override + protected void onPostExecute(SearchTaskResult result) { + progressDialog.cancel(); + if (result != null) { + // Ask the ReaderView to move to the resulting page + mDocView.setDisplayedViewIndex(result.pageNumber); + SearchTaskResult.set(result); + // Make the ReaderView act on the change to mSearchTaskResult + // via overridden onChildSetup method. + mDocView.resetupChildren(); + } else { + mAlertBuilder.setTitle(SearchTaskResult.get() == null ? R.string.text_not_found : R.string.no_further_occurences_found); + AlertDialog alert = mAlertBuilder.create(); + alert.setButton(AlertDialog.BUTTON_POSITIVE, "Dismiss", + (DialogInterface.OnClickListener)null); + alert.show(); + } + } + + @Override + protected void onCancelled() { + super.onCancelled(); + progressDialog.cancel(); + } + + @Override + protected void onProgressUpdate(Integer... values) { + super.onProgressUpdate(values); + progressDialog.setProgress(values[0].intValue()); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + mHandler.postDelayed(new Runnable() { + public void run() { + if (!progressDialog.isCancelled()) + { + progressDialog.show(); + progressDialog.setProgress(startIndex); + } + } + }, SEARCH_PROGRESS_DELAY); + } + }; + + mSearchTask.execute(); + } + + @Override + public boolean onSearchRequested() { + if (mButtonsVisible && mTopBarIsSearch) { + hideButtons(); + } else { + showButtons(); + searchModeOn(); + } + return super.onSearchRequested(); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + if (mButtonsVisible && !mTopBarIsSearch) { + hideButtons(); + } else { + showButtons(); + searchModeOff(); + } + return super.onPrepareOptionsMenu(menu); + } + + @Override + protected void onStart() { + if (core != null) + core.startAlerts(); + + createAlertWaiter(); + super.onStart(); + } + + @Override + protected void onStop() { + destroyAlertWaiter(); + if (core != null) + core.stopAlerts(); + + super.onStop(); + } + + @Override + public void onBackPressed() { + if (core.hasChanges()) { + DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (which == AlertDialog.BUTTON_POSITIVE) + core.save(); + + finish(); + } + }; + AlertDialog alert = mAlertBuilder.create(); + alert.setTitle("MuPDF"); + alert.setMessage("Document has changes. Save them?"); + alert.setButton(AlertDialog.BUTTON_POSITIVE, "Yes", listener); + alert.setButton(AlertDialog.BUTTON_NEGATIVE, "No", listener); + alert.show(); + } else { + super.onBackPressed(); + } + } +} diff --git a/android/src/com/artifex/mupdfdemo/MuPDFAlert.java b/android/src/com/artifex/mupdfdemo/MuPDFAlert.java new file mode 100644 index 00000000..76ed3a65 --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/MuPDFAlert.java @@ -0,0 +1,21 @@ +package com.artifex.mupdfdemo; + +public class MuPDFAlert { + public enum IconType {Error,Warning,Question,Status}; + public enum ButtonPressed {None,Ok,Cancel,No,Yes}; + public enum ButtonGroupType {Ok,OkCancel,YesNo,YesNoCancel}; + + public final String message; + public final IconType iconType; + public final ButtonGroupType buttonGroupType; + public final String title; + public ButtonPressed buttonPressed; + + MuPDFAlert(String aMessage, IconType aIconType, ButtonGroupType aButtonGroupType, String aTitle, ButtonPressed aButtonPressed) { + message = aMessage; + iconType = aIconType; + buttonGroupType = aButtonGroupType; + title = aTitle; + buttonPressed = aButtonPressed; + } +} diff --git a/android/src/com/artifex/mupdfdemo/MuPDFAlertInternal.java b/android/src/com/artifex/mupdfdemo/MuPDFAlertInternal.java new file mode 100644 index 00000000..5d65768f --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/MuPDFAlertInternal.java @@ -0,0 +1,30 @@ +package com.artifex.mupdfdemo; + +// Version of MuPDFAlert without enums to simplify JNI +public class MuPDFAlertInternal { + public final String message; + public final int iconType; + public final int buttonGroupType; + public final String title; + public int buttonPressed; + + MuPDFAlertInternal(String aMessage, int aIconType, int aButtonGroupType, String aTitle, int aButtonPressed) { + message = aMessage; + iconType = aIconType; + buttonGroupType = aButtonGroupType; + title = aTitle; + buttonPressed = aButtonPressed; + } + + MuPDFAlertInternal(MuPDFAlert alert) { + message = alert.message; + iconType = alert.iconType.ordinal(); + buttonGroupType = alert.buttonGroupType.ordinal(); + title = alert.message; + buttonPressed = alert.buttonPressed.ordinal(); + } + + MuPDFAlert toAlert() { + return new MuPDFAlert(message, MuPDFAlert.IconType.values()[iconType], MuPDFAlert.ButtonGroupType.values()[buttonGroupType], title, MuPDFAlert.ButtonPressed.values()[buttonPressed]); + } +} diff --git a/android/src/com/artifex/mupdfdemo/MuPDFCore.java b/android/src/com/artifex/mupdfdemo/MuPDFCore.java new file mode 100644 index 00000000..1e5c0686 --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/MuPDFCore.java @@ -0,0 +1,251 @@ +package com.artifex.mupdfdemo; +import java.util.ArrayList; + +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.PointF; +import android.graphics.RectF; + +public class MuPDFCore +{ + /* load our native library */ + static { + System.loadLibrary("mupdf"); + } + + /* Readable members */ + private int numPages = -1; + private float pageWidth; + private float pageHeight; + private long globals; + + /* The native functions */ + private native long openFile(String filename); + private native int countPagesInternal(); + private native void gotoPageInternal(int localActionPageNum); + private native float getPageWidth(); + private native float getPageHeight(); + private native void drawPage(Bitmap bitmap, + int pageW, int pageH, + int patchX, int patchY, + int patchW, int patchH); + private native void updatePageInternal(Bitmap bitmap, + int page, + int pageW, int pageH, + int patchX, int patchY, + int patchW, int patchH); + private native RectF[] searchPage(String text); + private native TextChar[][][][] text(); + private native int passClickEventInternal(int page, float x, float y); + private native void setFocusedWidgetChoiceSelectedInternal(String [] selected); + private native String [] getFocusedWidgetChoiceSelected(); + private native String [] getFocusedWidgetChoiceOptions(); + private native int setFocusedWidgetTextInternal(String text); + private native String getFocusedWidgetTextInternal(); + private native int getFocusedWidgetTypeInternal(); + private native LinkInfo [] getPageLinksInternal(int page); + private native RectF[] getWidgetAreasInternal(int page); + private native OutlineItem [] getOutlineInternal(); + private native boolean hasOutlineInternal(); + private native boolean needsPasswordInternal(); + private native boolean authenticatePasswordInternal(String password); + private native MuPDFAlertInternal waitForAlertInternal(); + private native void replyToAlertInternal(MuPDFAlertInternal alert); + private native void startAlertsInternal(); + private native void stopAlertsInternal(); + private native void destroying(); + private native boolean hasChangesInternal(); + private native void saveInternal(); + + public static native boolean javascriptSupported(); + + public MuPDFCore(String filename) throws Exception + { + globals = openFile(filename); + if (globals == 0) + { + throw new Exception("Failed to open "+filename); + } + } + + public int countPages() + { + if (numPages < 0) + numPages = countPagesSynchronized(); + + return numPages; + } + + private synchronized int countPagesSynchronized() { + return countPagesInternal(); + } + + /* Shim function */ + private void gotoPage(int page) + { + if (page > numPages-1) + page = numPages-1; + else if (page < 0) + page = 0; + gotoPageInternal(page); + this.pageWidth = getPageWidth(); + this.pageHeight = getPageHeight(); + } + + public synchronized PointF getPageSize(int page) { + gotoPage(page); + return new PointF(pageWidth, pageHeight); + } + + public MuPDFAlert waitForAlert() { + MuPDFAlertInternal alert = waitForAlertInternal(); + return alert != null ? alert.toAlert() : null; + } + + public void replyToAlert(MuPDFAlert alert) { + replyToAlertInternal(new MuPDFAlertInternal(alert)); + } + + public void stopAlerts() { + stopAlertsInternal(); + } + + public void startAlerts() { + startAlertsInternal(); + } + + public synchronized void onDestroy() { + destroying(); + globals = 0; + } + + public synchronized Bitmap drawPage(BitmapHolder h, int page, + int pageW, int pageH, + int patchX, int patchY, + int patchW, int patchH) { + gotoPage(page); + // Clear the reference out before creating the new bitmap + h.setBm(null); + Bitmap bm = Bitmap.createBitmap(patchW, patchH, Config.ARGB_8888); + drawPage(bm, pageW, pageH, patchX, patchY, patchW, patchH); + return bm; + } + + public synchronized Bitmap updatePage(BitmapHolder h, int page, + int pageW, int pageH, + int patchX, int patchY, + int patchW, int patchH) { + Bitmap bm = null; + Bitmap old_bm = h.getBm(); + + if (old_bm == null) + return null; + + bm = old_bm.copy(Bitmap.Config.ARGB_8888, false); + old_bm = null; + + updatePageInternal(bm, page, pageW, pageH, patchX, patchY, patchW, patchH); + return bm; + } + + public synchronized PassClickResult passClickEvent(int page, float x, float y) { + boolean changed = passClickEventInternal(page, x, y) != 0; + + switch (WidgetType.values()[getFocusedWidgetTypeInternal()]) + { + case TEXT: + return new PassClickResultText(changed, getFocusedWidgetTextInternal()); + case LISTBOX: + case COMBOBOX: + return new PassClickResultChoice(changed, getFocusedWidgetChoiceOptions(), getFocusedWidgetChoiceSelected()); + default: + return new PassClickResult(changed); + } + + } + + public synchronized boolean setFocusedWidgetText(int page, String text) { + boolean success; + gotoPage(page); + success = setFocusedWidgetTextInternal(text) != 0 ? true : false; + + return success; + } + + public synchronized void setFocusedWidgetChoiceSelected(String [] selected) { + setFocusedWidgetChoiceSelectedInternal(selected); + } + + public synchronized LinkInfo [] getPageLinks(int page) { + return getPageLinksInternal(page); + } + + public synchronized RectF [] getWidgetAreas(int page) { + return getWidgetAreasInternal(page); + } + + public synchronized RectF [] searchPage(int page, String text) { + gotoPage(page); + return searchPage(text); + } + + public synchronized TextWord [][] textLines(int page) { + gotoPage(page); + TextChar[][][][] chars = text(); + + // The text of the page held in a hierarchy (blocks, lines, spans). + // Currently we don't need to distinguish the blocks level or + // the spans, and we need to collect the text into words. + ArrayList lns = new ArrayList(); + + for (TextChar[][][] bl: chars) { + for (TextChar[][] ln: bl) { + ArrayList wds = new ArrayList(); + TextWord wd = new TextWord(); + + for (TextChar[] sp: ln) { + for (TextChar tc: sp) { + if (tc.c != ' ') { + wd.Add(tc); + } else if (wd.w.length() > 0) { + wds.add(wd); + wd = new TextWord(); + } + } + } + + if (wd.w.length() > 0) + wds.add(wd); + + if (wds.size() > 0) + lns.add(wds.toArray(new TextWord[wds.size()])); + } + } + + return lns.toArray(new TextWord[lns.size()][]); + } + + public synchronized boolean hasOutline() { + return hasOutlineInternal(); + } + + public synchronized OutlineItem [] getOutline() { + return getOutlineInternal(); + } + + public synchronized boolean needsPassword() { + return needsPasswordInternal(); + } + + public synchronized boolean authenticatePassword(String password) { + return authenticatePasswordInternal(password); + } + + public synchronized boolean hasChanges() { + return hasChangesInternal(); + } + + public synchronized void save() { + saveInternal(); + } +} diff --git a/android/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java b/android/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java new file mode 100644 index 00000000..806d0830 --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java @@ -0,0 +1,72 @@ +package com.artifex.mupdfdemo; + +import android.content.Context; +import android.graphics.Point; +import android.graphics.PointF; +import android.util.SparseArray; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +public class MuPDFPageAdapter extends BaseAdapter { + private final Context mContext; + private final MuPDFCore mCore; + private final SparseArray mPageSizes = new SparseArray(); + + public MuPDFPageAdapter(Context c, MuPDFCore core) { + mContext = c; + mCore = core; + } + + public int getCount() { + return mCore.countPages(); + } + + public Object getItem(int position) { + return null; + } + + public long getItemId(int position) { + return 0; + } + + public View getView(final int position, View convertView, ViewGroup parent) { + final MuPDFPageView pageView; + if (convertView == null) { + pageView = new MuPDFPageView(mContext, mCore, new Point(parent.getWidth(), parent.getHeight())); + } else { + pageView = (MuPDFPageView) convertView; + } + + PointF pageSize = mPageSizes.get(position); + if (pageSize != null) { + // We already know the page size. Set it up + // immediately + pageView.setPage(position, pageSize); + } else { + // Page size as yet unknown. Blank it for now, and + // start a background task to find the size + pageView.blank(position); + AsyncTask sizingTask = new AsyncTask() { + @Override + protected PointF doInBackground(Void... arg0) { + return mCore.getPageSize(position); + } + + @Override + protected void onPostExecute(PointF result) { + super.onPostExecute(result); + // We now know the page size + mPageSizes.put(position, result); + // Check that this view hasn't been reused for + // another page since we started + if (pageView.getPage() == position) + pageView.setPage(position, result); + } + }; + + sizingTask.execute((Void)null); + } + return pageView; + } +} diff --git a/android/src/com/artifex/mupdfdemo/MuPDFPageView.java b/android/src/com/artifex/mupdfdemo/MuPDFPageView.java new file mode 100644 index 00000000..c975cc70 --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/MuPDFPageView.java @@ -0,0 +1,243 @@ +package com.artifex.mupdfdemo; + +import android.app.AlertDialog; +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.view.LayoutInflater; +import android.view.WindowManager; +import android.widget.EditText; + +abstract class PassClickResultVisitor { + public abstract void visitText(PassClickResultText result); + public abstract void visitChoice(PassClickResultChoice 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); + } +} + +public class MuPDFPageView extends PageView { + private final MuPDFCore mCore; + private AsyncTask mPassClick; + private RectF mWidgetAreas[]; + private AsyncTask mLoadWidgetAreas; + private AlertDialog.Builder mTextEntryBuilder; + private AlertDialog.Builder mChoiceEntryBuilder; + private AlertDialog mTextEntry; + private EditText mEditText; + private AsyncTask mSetWidgetText; + private AsyncTask mSetWidgetChoice; + private Runnable changeReporter; + + public MuPDFPageView(Context c, MuPDFCore core, Point parentSize) { + super(c, parentSize); + mCore = core; + mTextEntryBuilder = new AlertDialog.Builder(c); + mTextEntryBuilder.setTitle("MuPDF: 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("Cancel", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + mTextEntryBuilder.setPositiveButton("Okay", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mSetWidgetText = new AsyncTask () { + @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("MuPDF: choose value"); + } + + 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() { + @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(); + } + + public void setChangeReporter(Runnable reporter) { + changeReporter = reporter; + } + + public boolean 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 hitWidget = false; + + if (mWidgetAreas != null) { + for (int i = 0; i < mWidgetAreas.length && !hitWidget; i++) + if (mWidgetAreas[i].contains(docRelX, docRelY)) + hitWidget = true; + } + + if (hitWidget) { + mPassClick = new AsyncTask() { + @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); + } + }); + } + }; + + mPassClick.execute(); + } + + return hitWidget; + } + + @Override + protected Bitmap drawPage(BitmapHolder h, int sizeX, int sizeY, + int patchX, int patchY, int patchWidth, int patchHeight) { + return mCore.drawPage(h, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight); + } + + @Override + protected Bitmap updatePage(BitmapHolder h, int sizeX, int sizeY, + int patchX, int patchY, int patchWidth, int patchHeight) { + return mCore.updatePage(h, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight); + } + + @Override + protected LinkInfo[] getLinkInfo() { + return mCore.getPageLinks(mPageNumber); + } + + @Override + protected TextWord[][] getText() { + return mCore.textLines(mPageNumber); + } + + @Override + public void setPage(final int page, PointF size) { + mLoadWidgetAreas = new AsyncTask () { + @Override + protected RectF[] doInBackground(Void... arg0) { + return mCore.getWidgetAreas(page); + } + + @Override + protected void onPostExecute(RectF[] result) { + mWidgetAreas = result; + } + }; + + mLoadWidgetAreas.execute(); + + super.setPage(page, size); + } +} diff --git a/android/src/com/artifex/mupdfdemo/OutlineActivity.java b/android/src/com/artifex/mupdfdemo/OutlineActivity.java new file mode 100644 index 00000000..bd1b19ba --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/OutlineActivity.java @@ -0,0 +1,30 @@ +package com.artifex.mupdfdemo; + +import android.app.ListActivity; +import android.os.Bundle; +import android.view.View; +import android.widget.ListView; + +public class OutlineActivity extends ListActivity { + OutlineItem mItems[]; + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mItems = OutlineActivityData.get().items; + setListAdapter(new OutlineAdapter(getLayoutInflater(),mItems)); + // Restore the position within the list from last viewing + getListView().setSelection(OutlineActivityData.get().position); + getListView().setDividerHeight(0); + setResult(-1); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + super.onListItemClick(l, v, position, id); + OutlineActivityData.get().position = getListView().getFirstVisiblePosition(); + setResult(mItems[position].page); + finish(); + } +} diff --git a/android/src/com/artifex/mupdfdemo/OutlineActivityData.java b/android/src/com/artifex/mupdfdemo/OutlineActivityData.java new file mode 100644 index 00000000..a703e61e --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/OutlineActivityData.java @@ -0,0 +1,17 @@ +package com.artifex.mupdfdemo; + +public class OutlineActivityData { + public OutlineItem items[]; + public int position; + static private OutlineActivityData singleton; + + static public void set(OutlineActivityData d) { + singleton = d; + } + + static public OutlineActivityData get() { + if (singleton == null) + singleton = new OutlineActivityData(); + return singleton; + } +} diff --git a/android/src/com/artifex/mupdfdemo/OutlineAdapter.java b/android/src/com/artifex/mupdfdemo/OutlineAdapter.java new file mode 100644 index 00000000..4251ed8e --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/OutlineAdapter.java @@ -0,0 +1,46 @@ +package com.artifex.mupdfdemo; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +public class OutlineAdapter extends BaseAdapter { + private final OutlineItem mItems[]; + private final LayoutInflater mInflater; + public OutlineAdapter(LayoutInflater inflater, OutlineItem items[]) { + mInflater = inflater; + mItems = items; + } + + public int getCount() { + return mItems.length; + } + + public Object getItem(int arg0) { + return null; + } + + public long getItemId(int arg0) { + return 0; + } + + public View getView(int position, View convertView, ViewGroup parent) { + View v; + if (convertView == null) { + v = mInflater.inflate(R.layout.outline_entry, null); + } else { + v = convertView; + } + int level = mItems[position].level; + if (level > 8) level = 8; + String space = ""; + for (int i=0; i lines = new ArrayList(); + for (TextWord[] line : mText) + if (line[0].bottom > mSelectBox.top && line[0].top < mSelectBox.bottom) + lines.add(line); + + Iterator it = lines.iterator(); + while (it.hasNext()) { + TextWord[] line = it.next(); + boolean firstLine = line[0].top < mSelectBox.top; + boolean lastLine = line[0].bottom > mSelectBox.bottom; + float start = Float.NEGATIVE_INFINITY; + float end = Float.POSITIVE_INFINITY; + + if (firstLine && lastLine) { + start = Math.min(mSelectBox.left, mSelectBox.right); + end = Math.max(mSelectBox.left, mSelectBox.right); + } else if (firstLine) { + start = mSelectBox.left; + } else if (lastLine) { + end = mSelectBox.right; + } + + onStartLine(); + + for (TextWord word : line) + if (word.right > start && word.left < end) + onWord(word); + + onEndLine(); + } + } +} + +public abstract class PageView extends ViewGroup { + private static final int HIGHLIGHT_COLOR = 0x802572AC; + private static final int LINK_COLOR = 0x80AC7225; + private static final int BACKGROUND_COLOR = 0xFFFFFFFF; + private static final int PROGRESS_DIALOG_DELAY = 200; + private final Context mContext; + protected int mPageNumber; + private Point mParentSize; + protected Point mSize; // Size of page at minimum zoom + protected float mSourceScale; + + private ImageView mEntire; // Image rendered at minimum zoom + private BitmapHolder mEntireBmh; + private AsyncTask mGetText; + private AsyncTask mGetLinkInfo; + private AsyncTask mDrawEntire; + + private Point mPatchViewSize; // View size on the basis of which the patch was created + private Rect mPatchArea; + private ImageView mPatch; + private BitmapHolder mPatchBmh; + private AsyncTask mDrawPatch; + private RectF mSearchBoxes[]; + protected LinkInfo mLinks[]; + private RectF mSelectBox; + private TextWord mText[][]; + private View mSearchView; + private boolean mIsBlank; + private boolean mHighlightLinks; + + private ProgressBar mBusyIndicator; + private final Handler mHandler = new Handler(); + + public PageView(Context c, Point parentSize) { + super(c); + mContext = c; + mParentSize = parentSize; + setBackgroundColor(BACKGROUND_COLOR); + mEntireBmh = new BitmapHolder(); + mPatchBmh = new BitmapHolder(); + } + + protected abstract Bitmap drawPage(BitmapHolder h, int sizeX, int sizeY, int patchX, int patchY, int patchWidth, int patchHeight); + protected abstract Bitmap updatePage(BitmapHolder h, int sizeX, int sizeY, int patchX, int patchY, int patchWidth, int patchHeight); + protected abstract LinkInfo[] getLinkInfo(); + protected abstract TextWord[][] getText(); + + private void reinit() { + // Cancel pending render task + if (mDrawEntire != null) { + mDrawEntire.cancel(true); + mDrawEntire = null; + } + + if (mDrawPatch != null) { + mDrawPatch.cancel(true); + mDrawPatch = null; + } + + if (mGetLinkInfo != null) { + mGetLinkInfo.cancel(true); + mGetLinkInfo = null; + } + + if (mGetText != null) { + mGetText.cancel(true); + mGetText = null; + } + + mIsBlank = true; + mPageNumber = 0; + + if (mSize == null) + mSize = mParentSize; + + if (mEntire != null) { + mEntire.setImageBitmap(null); + mEntireBmh.setBm(null); + } + + if (mPatch != null) { + mPatch.setImageBitmap(null); + mPatchBmh.setBm(null); + } + + mPatchViewSize = null; + mPatchArea = null; + + mSearchBoxes = null; + mLinks = null; + mSelectBox = null; + mText = null; + } + + public void releaseResources() { + reinit(); + + if (mBusyIndicator != null) { + removeView(mBusyIndicator); + mBusyIndicator = null; + } + } + + public void blank(int page) { + reinit(); + mPageNumber = page; + + if (mBusyIndicator == null) { + mBusyIndicator = new ProgressBar(mContext); + mBusyIndicator.setIndeterminate(true); + mBusyIndicator.setBackgroundResource(R.drawable.busy); + addView(mBusyIndicator); + } + } + + public void setPage(int page, PointF size) { + // Cancel pending render task + if (mDrawEntire != null) { + mDrawEntire.cancel(true); + mDrawEntire = null; + } + + mIsBlank = false; + + mPageNumber = page; + if (mEntire == null) { + mEntire = new OpaqueImageView(mContext); + mEntire.setScaleType(ImageView.ScaleType.FIT_CENTER); + addView(mEntire); + } + + // Calculate scaled size that fits within the screen limits + // This is the size at minimum zoom + mSourceScale = Math.min(mParentSize.x/size.x, mParentSize.y/size.y); + Point newSize = new Point((int)(size.x*mSourceScale), (int)(size.y*mSourceScale)); + mSize = newSize; + + mEntire.setImageBitmap(null); + mEntireBmh.setBm(null); + + // Get the link info in the background + mGetLinkInfo = new AsyncTask() { + protected LinkInfo[] doInBackground(Void... v) { + return getLinkInfo(); + } + + protected void onPostExecute(LinkInfo[] v) { + mLinks = v; + invalidate(); + } + }; + + mGetLinkInfo.execute(); + + // Render the page in the background + mDrawEntire = new AsyncTask() { + protected Bitmap doInBackground(Void... v) { + return drawPage(mEntireBmh, mSize.x, mSize.y, 0, 0, mSize.x, mSize.y); + } + + protected void onPreExecute() { + mEntire.setImageBitmap(null); + mEntireBmh.setBm(null); + + if (mBusyIndicator == null) { + mBusyIndicator = new ProgressBar(mContext); + mBusyIndicator.setIndeterminate(true); + mBusyIndicator.setBackgroundResource(R.drawable.busy); + addView(mBusyIndicator); + mBusyIndicator.setVisibility(INVISIBLE); + mHandler.postDelayed(new Runnable() { + public void run() { + if (mBusyIndicator != null) + mBusyIndicator.setVisibility(VISIBLE); + } + }, PROGRESS_DIALOG_DELAY); + } + } + + protected void onPostExecute(Bitmap bm) { + removeView(mBusyIndicator); + mBusyIndicator = null; + mEntire.setImageBitmap(bm); + mEntireBmh.setBm(bm); + invalidate(); + } + }; + + mDrawEntire.execute(); + + if (mSearchView == null) { + mSearchView = new View(mContext) { + @Override + protected void onDraw(final Canvas canvas) { + super.onDraw(canvas); + // Work out current total scale factor + // from source to view + final float scale = mSourceScale*(float)getWidth()/(float)mSize.x; + final Paint paint = new Paint(); + + if (!mIsBlank && mSearchBoxes != null) { + paint.setColor(HIGHLIGHT_COLOR); + for (RectF rect : mSearchBoxes) + canvas.drawRect(rect.left*scale, rect.top*scale, + rect.right*scale, rect.bottom*scale, + paint); + } + + if (!mIsBlank && mLinks != null && mHighlightLinks) { + paint.setColor(LINK_COLOR); + for (LinkInfo link : mLinks) + canvas.drawRect(link.rect.left*scale, link.rect.top*scale, + link.rect.right*scale, link.rect.bottom*scale, + paint); + } + + if (mSelectBox != null && mText != null) { + paint.setColor(HIGHLIGHT_COLOR); + TextSelector sel = new TextSelector(mText, mSelectBox) { + RectF rect; + + @Override + protected void onStartLine() { + rect = new RectF(); + } + + @Override + protected void onWord(TextWord word) { + rect.union(word); + } + + @Override + protected void onEndLine() { + if (!rect.isEmpty()) + canvas.drawRect(rect.left*scale, rect.top*scale, rect.right*scale, rect.bottom*scale, paint); + } + }; + + sel.select(); + } + } + }; + + addView(mSearchView); + } + requestLayout(); + } + + public void setSearchBoxes(RectF searchBoxes[]) { + mSearchBoxes = searchBoxes; + if (mSearchView != null) + mSearchView.invalidate(); + } + + public void setLinkHighlighting(boolean f) { + mHighlightLinks = f; + if (mSearchView != null) + mSearchView.invalidate(); + } + + public void deselectText() { + mSelectBox = null; + mSearchView.invalidate(); + } + + public void selectText(float x0, float y0, float x1, float y1) { + float scale = mSourceScale*(float)getWidth()/(float)mSize.x; + float docRelX0 = (x0 - getLeft())/scale; + float docRelY0 = (y0 - getTop())/scale; + float docRelX1 = (x1 - getLeft())/scale; + float docRelY1 = (y1 - getTop())/scale; + // Order on Y but maintain the point grouping + if (docRelY0 <= docRelY1) + mSelectBox = new RectF(docRelX0, docRelY0, docRelX1, docRelY1); + else + mSelectBox = new RectF(docRelX1, docRelY1, docRelX0, docRelY0); + + mSearchView.invalidate(); + + if (mGetText == null) { + mGetText = new AsyncTask() { + @Override + protected TextWord[][] doInBackground(Void... params) { + return getText(); + } + @Override + protected void onPostExecute(TextWord[][] result) { + mText = result; + mSearchView.invalidate(); + } + }; + + mGetText.execute(); + } + } + + public boolean copySelection() { + final StringBuilder text = new StringBuilder(); + + TextSelector sel = new TextSelector(mText, mSelectBox) { + StringBuilder line; + + @Override + protected void onStartLine() { + line = new StringBuilder(); + } + + @Override + protected void onWord(TextWord word) { + if (line.length() > 0) + line.append(' '); + line.append(word.w); + } + + @Override + protected void onEndLine() { + if (text.length() > 0) + text.append('\n'); + text.append(line); + } + }; + + sel.select(); + + if (text.length() == 0) + return false; + + ClipboardManager cm = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); + + cm.setPrimaryClip(ClipData.newPlainText("MuPDF", text)); + + mSelectBox = null; + mSearchView.invalidate(); + + return true; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int x, y; + switch(View.MeasureSpec.getMode(widthMeasureSpec)) { + case View.MeasureSpec.UNSPECIFIED: + x = mSize.x; + break; + default: + x = View.MeasureSpec.getSize(widthMeasureSpec); + } + switch(View.MeasureSpec.getMode(heightMeasureSpec)) { + case View.MeasureSpec.UNSPECIFIED: + y = mSize.y; + break; + default: + y = View.MeasureSpec.getSize(heightMeasureSpec); + } + + setMeasuredDimension(x, y); + + if (mBusyIndicator != null) { + int limit = Math.min(mParentSize.x, mParentSize.y)/2; + mBusyIndicator.measure(View.MeasureSpec.AT_MOST | limit, View.MeasureSpec.AT_MOST | limit); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int w = right-left; + int h = bottom-top; + + if (mEntire != null) { + mEntire.layout(0, 0, w, h); + } + + if (mSearchView != null) { + mSearchView.layout(0, 0, w, h); + } + + if (mPatchViewSize != null) { + if (mPatchViewSize.x != w || mPatchViewSize.y != h) { + // Zoomed since patch was created + mPatchViewSize = null; + mPatchArea = null; + if (mPatch != null) { + mPatch.setImageBitmap(null); + mPatchBmh.setBm(null); + } + } else { + mPatch.layout(mPatchArea.left, mPatchArea.top, mPatchArea.right, mPatchArea.bottom); + } + } + + if (mBusyIndicator != null) { + int bw = mBusyIndicator.getMeasuredWidth(); + int bh = mBusyIndicator.getMeasuredHeight(); + + mBusyIndicator.layout((w-bw)/2, (h-bh)/2, (w+bw)/2, (h+bh)/2); + } + } + + public void addHq(boolean update) { + Rect viewArea = new Rect(getLeft(),getTop(),getRight(),getBottom()); + // If the viewArea's size matches the unzoomed size, there is no need for an hq patch + if (viewArea.width() != mSize.x || viewArea.height() != mSize.y) { + Point patchViewSize = new Point(viewArea.width(), viewArea.height()); + Rect patchArea = new Rect(0, 0, mParentSize.x, mParentSize.y); + + // Intersect and test that there is an intersection + if (!patchArea.intersect(viewArea)) + return; + + // Offset patch area to be relative to the view top left + patchArea.offset(-viewArea.left, -viewArea.top); + + boolean area_unchanged = patchArea.equals(mPatchArea) && patchViewSize.equals(mPatchViewSize); + + // If being asked for the same area as last time and not because of an update then nothing to do + if (area_unchanged && !update) + return; + + boolean completeRedraw = !(area_unchanged && update); + + // Stop the drawing of previous patch if still going + if (mDrawPatch != null) { + mDrawPatch.cancel(true); + mDrawPatch = null; + } + + if (completeRedraw) { + // The bitmap holder mPatchBm may still be rendered to by a + // previously invoked task, and possibly for a different + // area, so we cannot risk the bitmap generated by this task + // being passed to it + mPatchBmh.setBm(null); + mPatchBmh = new BitmapHolder(); + } + + // Create and add the image view if not already done + if (mPatch == null) { + mPatch = new OpaqueImageView(mContext); + mPatch.setScaleType(ImageView.ScaleType.FIT_CENTER); + addView(mPatch); + mSearchView.bringToFront(); + } + + mDrawPatch = new AsyncTask() { + protected PatchInfo doInBackground(PatchInfo... v) { + if (v[0].completeRedraw) { + v[0].bm = drawPage(v[0].bmh, v[0].patchViewSize.x, v[0].patchViewSize.y, + v[0].patchArea.left, v[0].patchArea.top, + v[0].patchArea.width(), v[0].patchArea.height()); + } else { + v[0].bm = updatePage(v[0].bmh, v[0].patchViewSize.x, v[0].patchViewSize.y, + v[0].patchArea.left, v[0].patchArea.top, + v[0].patchArea.width(), v[0].patchArea.height()); + } + + return v[0]; + } + + protected void onPostExecute(PatchInfo v) { + if (mPatchBmh == v.bmh) { + mPatchViewSize = v.patchViewSize; + mPatchArea = v.patchArea; + if (v.bm != null) { + mPatch.setImageBitmap(v.bm); + v.bmh.setBm(v.bm); + v.bm = null; + } + //requestLayout(); + // Calling requestLayout here doesn't lead to a later call to layout. No idea + // why, but apparently others have run into the problem. + mPatch.layout(mPatchArea.left, mPatchArea.top, mPatchArea.right, mPatchArea.bottom); + invalidate(); + } + } + }; + + mDrawPatch.execute(new PatchInfo(patchViewSize, patchArea, mPatchBmh, completeRedraw)); + } + } + + public void update() { + // Cancel pending render task + if (mDrawEntire != null) { + mDrawEntire.cancel(true); + mDrawEntire = null; + } + + if (mDrawPatch != null) { + mDrawPatch.cancel(true); + mDrawPatch = null; + } + + // Render the page in the background + mDrawEntire = new AsyncTask() { + protected Bitmap doInBackground(Void... v) { + // Pass the current bitmap as a basis for the update, but use a bitmap + // holder so that the held bitmap will be nulled and not hold on to + // memory, should this view become redundant. + return updatePage(mEntireBmh, mSize.x, mSize.y, 0, 0, mSize.x, mSize.y); + } + + protected void onPostExecute(Bitmap bm) { + if (bm != null) { + mEntire.setImageBitmap(bm); + mEntireBmh.setBm(bm); + } + invalidate(); + } + }; + + mDrawEntire.execute(); + + addHq(true); + } + + public void removeHq() { + // Stop the drawing of the patch if still going + if (mDrawPatch != null) { + mDrawPatch.cancel(true); + mDrawPatch = null; + } + + // And get rid of it + mPatchViewSize = null; + mPatchArea = null; + if (mPatch != null) { + mPatch.setImageBitmap(null); + mPatchBmh.setBm(null); + } + } + + public int getPage() { + return mPageNumber; + } + + @Override + public boolean isOpaque() { + return true; + } +} diff --git a/android/src/com/artifex/mupdfdemo/ReaderView.java b/android/src/com/artifex/mupdfdemo/ReaderView.java new file mode 100644 index 00000000..16ac1129 --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/ReaderView.java @@ -0,0 +1,583 @@ +package com.artifex.mupdfdemo; + +import java.util.LinkedList; +import java.util.NoSuchElementException; + +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; +import android.widget.Adapter; +import android.widget.AdapterView; +import android.widget.Scroller; + +public class ReaderView extends AdapterView + implements GestureDetector.OnGestureListener, + ScaleGestureDetector.OnScaleGestureListener, + Runnable { + private static final int MOVING_DIAGONALLY = 0; + private static final int MOVING_LEFT = 1; + private static final int MOVING_RIGHT = 2; + private static final int MOVING_UP = 3; + private static final int MOVING_DOWN = 4; + + private static final int FLING_MARGIN = 100; + private static final int GAP = 20; + + private static final float MIN_SCALE = 1.0f; + private static final float MAX_SCALE = 5.0f; + + private Adapter mAdapter; + private int mCurrent; // Adapter's index for the current view + private boolean mResetLayout; + private final SparseArray + mChildViews = new SparseArray(3); + // Shadows the children of the adapter view + // but with more sensible indexing + private final LinkedList + mViewCache = new LinkedList(); + private boolean mUserInteracting; // Whether the user is interacting + private boolean mScaling; // Whether the user is currently pinch zooming + private float mScale = 1.0f; + private int mXScroll; // Scroll amounts recorded from events. + private int mYScroll; // and then accounted for in onLayout + private final GestureDetector + mGestureDetector; + private final ScaleGestureDetector + mScaleGestureDetector; + private final Scroller mScroller; + private int mScrollerLastX; + private int mScrollerLastY; + private boolean mScrollDisabled; + + static abstract class ViewMapper { + abstract void applyToView(View view); + } + + public ReaderView(Context context) { + super(context); + mGestureDetector = new GestureDetector(this); + mScaleGestureDetector = new ScaleGestureDetector(context, this); + mScroller = new Scroller(context); + } + + public ReaderView(Context context, AttributeSet attrs) { + super(context, attrs); + mGestureDetector = new GestureDetector(this); + mScaleGestureDetector = new ScaleGestureDetector(context, this); + mScroller = new Scroller(context); + } + + public ReaderView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mGestureDetector = new GestureDetector(this); + mScaleGestureDetector = new ScaleGestureDetector(context, this); + mScroller = new Scroller(context); + } + + public int getDisplayedViewIndex() { + return mCurrent; + } + + public void setDisplayedViewIndex(int i) { + if (0 <= i && i < mAdapter.getCount()) { + mCurrent = i; + onMoveToChild(i); + mResetLayout = true; + requestLayout(); + } + } + + public void moveToNext() { + View v = mChildViews.get(mCurrent+1); + if (v != null) + slideViewOntoScreen(v); + } + + public void moveToPrevious() { + View v = mChildViews.get(mCurrent-1); + if (v != null) + slideViewOntoScreen(v); + } + + public void resetupChildren() { + for (int i = 0; i < mChildViews.size(); i++) + onChildSetup(mChildViews.keyAt(i), mChildViews.valueAt(i)); + } + + public void applyToChildren(ViewMapper mapper) { + for (int i = 0; i < mChildViews.size(); i++) + mapper.applyToView(mChildViews.valueAt(i)); + } + + protected void onChildSetup(int i, View v) {} + + protected void onMoveToChild(int i) {} + + protected void onSettle(View v) {}; + + protected void onUnsettle(View v) {}; + + protected void onNotInUse(View v) {}; + + public View getDisplayedView() { + return mChildViews.get(mCurrent); + } + + public void run() { + if (!mScroller.isFinished()) { + mScroller.computeScrollOffset(); + int x = mScroller.getCurrX(); + int y = mScroller.getCurrY(); + mXScroll += x - mScrollerLastX; + mYScroll += y - mScrollerLastY; + mScrollerLastX = x; + mScrollerLastY = y; + requestLayout(); + post(this); + } + else if (!mUserInteracting) { + // End of an inertial scroll and the user is not interacting. + // The layout is stable + View v = mChildViews.get(mCurrent); + if (v != null) + postSettle(v); + } + } + + public boolean onDown(MotionEvent arg0) { + mScroller.forceFinished(true); + return true; + } + + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + if (mScrollDisabled) + return true; + + View v = mChildViews.get(mCurrent); + if (v != null) { + Rect bounds = getScrollBounds(v); + switch(directionOfTravel(velocityX, velocityY)) { + case MOVING_LEFT: + if (bounds.left >= 0) { + // Fling off to the left bring next view onto screen + View vl = mChildViews.get(mCurrent+1); + + if (vl != null) { + slideViewOntoScreen(vl); + return true; + } + } + break; + case MOVING_RIGHT: + if (bounds.right <= 0) { + // Fling off to the right bring previous view onto screen + View vr = mChildViews.get(mCurrent-1); + + if (vr != null) { + slideViewOntoScreen(vr); + return true; + } + } + break; + } + mScrollerLastX = mScrollerLastY = 0; + // If the page has been dragged out of bounds then we want to spring back + // nicely. fling jumps back into bounds instantly, so we don't want to use + // fling in that case. On the other hand, we don't want to forgo a fling + // just because of a slightly off-angle drag taking us out of bounds other + // than in the direction of the drag, so we test for out of bounds only + // in the direction of travel. + // + // Also don't fling if out of bounds in any direction by more than fling + // margin + Rect expandedBounds = new Rect(bounds); + expandedBounds.inset(-FLING_MARGIN, -FLING_MARGIN); + + if(withinBoundsInDirectionOfTravel(bounds, velocityX, velocityY) + && expandedBounds.contains(0, 0)) { + mScroller.fling(0, 0, (int)velocityX, (int)velocityY, bounds.left, bounds.right, bounds.top, bounds.bottom); + post(this); + } + } + + return true; + } + + public void onLongPress(MotionEvent e) { + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, + float distanceY) { + if (!mScrollDisabled) { + mXScroll -= distanceX; + mYScroll -= distanceY; + requestLayout(); + } + return true; + } + + public void onShowPress(MotionEvent e) { + } + + public boolean onSingleTapUp(MotionEvent e) { + return false; + } + + public boolean onScale(ScaleGestureDetector detector) { + float previousScale = mScale; + mScale = Math.min(Math.max(mScale * detector.getScaleFactor(), MIN_SCALE), MAX_SCALE); + float factor = mScale/previousScale; + + View v = mChildViews.get(mCurrent); + if (v != null) { + // Work out the focus point relative to the view top left + int viewFocusX = (int)detector.getFocusX() - (v.getLeft() + mXScroll); + int viewFocusY = (int)detector.getFocusY() - (v.getTop() + mYScroll); + // Scroll to maintain the focus point + mXScroll += viewFocusX - viewFocusX * factor; + mYScroll += viewFocusY - viewFocusY * factor; + requestLayout(); + } + return true; + } + + public boolean onScaleBegin(ScaleGestureDetector detector) { + mScaling = true; + // Ignore any scroll amounts yet to be accounted for: the + // screen is not showing the effect of them, so they can + // only confuse the user + mXScroll = mYScroll = 0; + // Avoid jump at end of scaling by disabling scrolling + // until the next start of gesture + mScrollDisabled = true; + return true; + } + + public void onScaleEnd(ScaleGestureDetector detector) { + mScaling = false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + mScaleGestureDetector.onTouchEvent(event); + + if (!mScaling) + mGestureDetector.onTouchEvent(event); + + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mUserInteracting = true; + } + if (event.getActionMasked() == MotionEvent.ACTION_UP) { + mScrollDisabled = false; + mUserInteracting = false; + + View v = mChildViews.get(mCurrent); + if (v != null) { + if (mScroller.isFinished()) { + // If, at the end of user interaction, there is no + // current inertial scroll in operation then animate + // the view onto screen if necessary + slideViewOntoScreen(v); + } + + if (mScroller.isFinished()) { + // If still there is no inertial scroll in operation + // then the layout is stable + postSettle(v); + } + } + } + + requestLayout(); + return true; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int n = getChildCount(); + for (int i = 0; i < n; i++) + measureView(getChildAt(i)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, + int bottom) { + super.onLayout(changed, left, top, right, bottom); + + View cv = mChildViews.get(mCurrent); + Point cvOffset; + + if (!mResetLayout) { + // Move to next or previous if current is sufficiently off center + if (cv != null) { + cvOffset = subScreenSizeOffset(cv); + // cv.getRight() may be out of date with the current scale + // so add left to the measured width for the correct position + if (cv.getLeft() + cv.getMeasuredWidth() + cvOffset.x + GAP/2 + mXScroll < getWidth()/2 && mCurrent + 1 < mAdapter.getCount()) { + postUnsettle(cv); + // post to invoke test for end of animation + // where we must set hq area for the new current view + post(this); + + mCurrent++; + onMoveToChild(mCurrent); + } + + if (cv.getLeft() - cvOffset.x - GAP/2 + mXScroll >= getWidth()/2 && mCurrent > 0) { + postUnsettle(cv); + // post to invoke test for end of animation + // where we must set hq area for the new current view + post(this); + + mCurrent--; + onMoveToChild(mCurrent); + } + } + + // Remove not needed children and hold them for reuse + int numChildren = mChildViews.size(); + int childIndices[] = new int[numChildren]; + for (int i = 0; i < numChildren; i++) + childIndices[i] = mChildViews.keyAt(i); + + for (int i = 0; i < numChildren; i++) { + int ai = childIndices[i]; + if (ai < mCurrent - 1 || ai > mCurrent + 1) { + View v = mChildViews.get(ai); + onNotInUse(v); + mViewCache.add(v); + removeViewInLayout(v); + mChildViews.remove(ai); + } + } + } else { + mResetLayout = false; + mXScroll = mYScroll = 0; + + // Remove all children and hold them for reuse + int numChildren = mChildViews.size(); + for (int i = 0; i < numChildren; i++) { + View v = mChildViews.valueAt(i); + onNotInUse(v); + mViewCache.add(v); + removeViewInLayout(v); + } + mChildViews.clear(); + // post to ensure generation of hq area + post(this); + } + + // Ensure current view is present + int cvLeft, cvRight, cvTop, cvBottom; + boolean notPresent = (mChildViews.get(mCurrent) == null); + cv = getOrCreateChild(mCurrent); + // When the view is sub-screen-size in either dimension we + // offset it to center within the screen area, and to keep + // the views spaced out + cvOffset = subScreenSizeOffset(cv); + if (notPresent) { + //Main item not already present. Just place it top left + cvLeft = cvOffset.x; + cvTop = cvOffset.y; + } else { + // Main item already present. Adjust by scroll offsets + cvLeft = cv.getLeft() + mXScroll; + cvTop = cv.getTop() + mYScroll; + } + // Scroll values have been accounted for + mXScroll = mYScroll = 0; + cvRight = cvLeft + cv.getMeasuredWidth(); + cvBottom = cvTop + cv.getMeasuredHeight(); + + if (!mUserInteracting && mScroller.isFinished()) { + Point corr = getCorrection(getScrollBounds(cvLeft, cvTop, cvRight, cvBottom)); + cvRight += corr.x; + cvLeft += corr.x; + cvTop += corr.y; + cvBottom += corr.y; + } else if (cv.getMeasuredHeight() <= getHeight()) { + // When the current view is as small as the screen in height, clamp + // it vertically + Point corr = getCorrection(getScrollBounds(cvLeft, cvTop, cvRight, cvBottom)); + cvTop += corr.y; + cvBottom += corr.y; + } + + cv.layout(cvLeft, cvTop, cvRight, cvBottom); + + if (mCurrent > 0) { + View lv = getOrCreateChild(mCurrent - 1); + Point leftOffset = subScreenSizeOffset(lv); + int gap = leftOffset.x + GAP + cvOffset.x; + lv.layout(cvLeft - lv.getMeasuredWidth() - gap, + (cvBottom + cvTop - lv.getMeasuredHeight())/2, + cvLeft - gap, + (cvBottom + cvTop + lv.getMeasuredHeight())/2); + } + + if (mCurrent + 1 < mAdapter.getCount()) { + View rv = getOrCreateChild(mCurrent + 1); + Point rightOffset = subScreenSizeOffset(rv); + int gap = cvOffset.x + GAP + rightOffset.x; + rv.layout(cvRight + gap, + (cvBottom + cvTop - rv.getMeasuredHeight())/2, + cvRight + rv.getMeasuredWidth() + gap, + (cvBottom + cvTop + rv.getMeasuredHeight())/2); + } + + invalidate(); + } + + @Override + public Adapter getAdapter() { + return mAdapter; + } + + @Override + public View getSelectedView() { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public void setAdapter(Adapter adapter) { + mAdapter = adapter; + mChildViews.clear(); + removeAllViewsInLayout(); + requestLayout(); + } + + @Override + public void setSelection(int arg0) { + throw new UnsupportedOperationException("Not supported"); + } + + private View getCached() { + if (mViewCache.size() == 0) + return null; + else + return mViewCache.removeFirst(); + } + + private View getOrCreateChild(int i) { + View v = mChildViews.get(i); + if (v == null) { + v = mAdapter.getView(i, getCached(), this); + addAndMeasureChild(i, v); + } + onChildSetup(i, v); + + return v; + } + + private void addAndMeasureChild(int i, View v) { + LayoutParams params = v.getLayoutParams(); + if (params == null) { + params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + addViewInLayout(v, 0, params, true); + mChildViews.append(i, v); // Record the view against it's adapter index + measureView(v); + } + + private void measureView(View v) { + // See what size the view wants to be + v.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + // Work out a scale that will fit it to this view + float scale = Math.min((float)getWidth()/(float)v.getMeasuredWidth(), + (float)getHeight()/(float)v.getMeasuredHeight()); + // Use the fitting values scaled by our current scale factor + v.measure(View.MeasureSpec.EXACTLY | (int)(v.getMeasuredWidth()*scale*mScale), + View.MeasureSpec.EXACTLY | (int)(v.getMeasuredHeight()*scale*mScale)); + } + + private Rect getScrollBounds(int left, int top, int right, int bottom) { + int xmin = getWidth() - right; + int xmax = -left; + int ymin = getHeight() - bottom; + int ymax = -top; + + // In either dimension, if view smaller than screen then + // constrain it to be central + if (xmin > xmax) xmin = xmax = (xmin + xmax)/2; + if (ymin > ymax) ymin = ymax = (ymin + ymax)/2; + + return new Rect(xmin, ymin, xmax, ymax); + } + + private Rect getScrollBounds(View v) { + // There can be scroll amounts not yet accounted for in + // onLayout, so add mXScroll and mYScroll to the current + // positions when calculating the bounds. + return getScrollBounds(v.getLeft() + mXScroll, + v.getTop() + mYScroll, + v.getLeft() + v.getMeasuredWidth() + mXScroll, + v.getTop() + v.getMeasuredHeight() + mYScroll); + } + + private Point getCorrection(Rect bounds) { + return new Point(Math.min(Math.max(0,bounds.left),bounds.right), + Math.min(Math.max(0,bounds.top),bounds.bottom)); + } + + private void postSettle(final View v) { + // onSettle and onUnsettle are posted so that the calls + // wont be executed until after the system has performed + // layout. + post (new Runnable() { + public void run () { + onSettle(v); + } + }); + } + + private void postUnsettle(final View v) { + post (new Runnable() { + public void run () { + onUnsettle(v); + } + }); + } + + private void slideViewOntoScreen(View v) { + Point corr = getCorrection(getScrollBounds(v)); + if (corr.x != 0 || corr.y != 0) { + mScrollerLastX = mScrollerLastY = 0; + mScroller.startScroll(0, 0, corr.x, corr.y, 400); + post(this); + } + } + + private Point subScreenSizeOffset(View v) { + return new Point(Math.max((getWidth() - v.getMeasuredWidth())/2, 0), + Math.max((getHeight() - v.getMeasuredHeight())/2, 0)); + } + + private static int directionOfTravel(float vx, float vy) { + if (Math.abs(vx) > 2 * Math.abs(vy)) + return (vx > 0) ? MOVING_RIGHT : MOVING_LEFT; + else if (Math.abs(vy) > 2 * Math.abs(vx)) + return (vy > 0) ? MOVING_DOWN : MOVING_UP; + else + return MOVING_DIAGONALLY; + } + + private static boolean withinBoundsInDirectionOfTravel(Rect bounds, float vx, float vy) { + switch (directionOfTravel(vx, vy)) { + case MOVING_DIAGONALLY: return bounds.contains(0, 0); + case MOVING_LEFT: return bounds.left <= 0; + case MOVING_RIGHT: return bounds.right >= 0; + case MOVING_UP: return bounds.top <= 0; + case MOVING_DOWN: return bounds.bottom >= 0; + default: throw new NoSuchElementException(); + } + } +} diff --git a/android/src/com/artifex/mupdfdemo/TextChar.java b/android/src/com/artifex/mupdfdemo/TextChar.java new file mode 100644 index 00000000..aebf519f --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/TextChar.java @@ -0,0 +1,12 @@ +package com.artifex.mupdfdemo; + +import android.graphics.RectF; + +public class TextChar extends RectF { + public char c; + + public TextChar(float x0, float y0, float x1, float y1, char _c) { + super(x0, y0, x1, y1); + c = _c; + } +} diff --git a/android/src/com/artifex/mupdfdemo/TextWord.java b/android/src/com/artifex/mupdfdemo/TextWord.java new file mode 100644 index 00000000..d9672573 --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/TextWord.java @@ -0,0 +1,17 @@ +package com.artifex.mupdfdemo; + +import android.graphics.RectF; + +public class TextWord extends RectF { + public String w; + + public TextWord() { + super(); + w = new String(); + } + + public void Add(TextChar tc) { + super.union(tc); + w = w.concat(new String(new char[]{tc.c})); + } +} diff --git a/android/src/com/artifex/mupdfdemo/WidgetType.java b/android/src/com/artifex/mupdfdemo/WidgetType.java new file mode 100644 index 00000000..5a22975d --- /dev/null +++ b/android/src/com/artifex/mupdfdemo/WidgetType.java @@ -0,0 +1,8 @@ +package com.artifex.mupdfdemo; + +public enum WidgetType { + NONE, + TEXT, + LISTBOX, + COMBOBOX +} -- cgit v1.2.3