diff options
author | fred ross-perry <fredross-perry@Fred-Ross-Perrys-Computer.local> | 2016-07-19 10:53:40 -0700 |
---|---|---|
committer | fred ross-perry <fredross-perry@Fred-Ross-Perrys-Computer.local> | 2016-08-08 14:20:56 -0700 |
commit | 5e6e1793723b139a7ed702cf6dc48955fb27300e (patch) | |
tree | 62908437f3aeae2da759de02e5e10c65cf7a66e5 | |
parent | d0fcfca2a309623a04809f64de2846c439761c1d (diff) | |
download | mupdf-5e6e1793723b139a7ed702cf6dc48955fb27300e.tar.xz |
Android example - collected changes
- move UI into the "mupdf" module.
The app simply sets up a DocView and starts it with a file path.
The app can call DocView.showUI to determine whether the built-in UI
is used. The plan is for apps to be able to use the built-in UI,
or supply their own.
Also includes some rudimentary UI for show/hiding annotations, and
listing links and outlines. Just for testing JNI features.
- add toolbar and tabs at the top of the view.
- refactoring
DocView becomes DocActivityView, DocViewInner becomes DocView.
- add Pages toolbar and page list view.
make the page list a little narrower.
give the document view and the pages view separate bitmaps,
and have DocPageView implement clipping when it draws.
- fix tabbing and brace style.
- add simple text selection and highlighting.
This is still a work in progress.
- various code review items.
fix tabs and brace styles
remove function calls from loop statements
use "highlight", not "hilight"
put a few strings into resources
avoid Right Hand Drift (tm)
Rects are inclusive...exclusive for the purpose of testing limits
put "else" on a separate line
put "catch" and "finally" on separate lines
53 files changed, 3472 insertions, 997 deletions
diff --git a/platform/android/example/app/src/main/java/com/artifex/mupdf/example/ChooseDocActivity.java b/platform/android/example/app/src/main/java/com/artifex/mupdf/example/ChooseDocActivity.java index 3fcb3064..4561bc4a 100644 --- a/platform/android/example/app/src/main/java/com/artifex/mupdf/example/ChooseDocActivity.java +++ b/platform/android/example/app/src/main/java/com/artifex/mupdf/example/ChooseDocActivity.java @@ -43,25 +43,28 @@ public class ChooseDocActivity String storageState = Environment.getExternalStorageState(); if (!Environment.MEDIA_MOUNTED.equals(storageState) && - !Environment.MEDIA_MOUNTED_READ_ONLY.equals(storageState)) + !Environment.MEDIA_MOUNTED_READ_ONLY.equals(storageState)) { showMessage(getResources().getString(R.string.no_media_warning), - getResources().getString(R.string.no_media_hint), - getResources().getString(R.string.dismiss)); + getResources().getString(R.string.no_media_hint), + getResources().getString(R.string.dismiss)); return; } - if (mDirectory == null) { + if (mDirectory == null) + { mDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); mStartingDirectory = mDirectory; // remember where we started } // Create the list... - mListView = (ListView)findViewById(R.id.fileListView); - mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + mListView = (ListView) findViewById(R.id.fileListView); + mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() + { @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + public void onItemClick(AdapterView<?> parent, View view, int position, long id) + { onListItemClick(mListView, view, position, id); } }); @@ -71,8 +74,10 @@ public class ChooseDocActivity mListView.setAdapter(adapter); // ...that is updated dynamically when files are scanned - mUpdateFiles = new Runnable() { - public void run() { + mUpdateFiles = new Runnable() + { + public void run() + { Resources res = getResources(); String appName = res.getString(R.string.app_name); String version = res.getString(R.string.version); @@ -81,18 +86,22 @@ public class ChooseDocActivity mParent = mDirectory.getParentFile(); - mDirs = mDirectory.listFiles(new FileFilter() { + mDirs = mDirectory.listFiles(new FileFilter() + { - public boolean accept(File file) { + public boolean accept(File file) + { return file.isDirectory(); } }); if (mDirs == null) mDirs = new File[0]; - mFiles = mDirectory.listFiles(new FileFilter() { + mFiles = mDirectory.listFiles(new FileFilter() + { - public boolean accept(File file) { + public boolean accept(File file) + { if (file.isDirectory()) return false; @@ -129,14 +138,18 @@ public class ChooseDocActivity if (mFiles == null) mFiles = new File[0]; - Arrays.sort(mFiles, new Comparator<File>() { - public int compare(File arg0, File arg1) { + Arrays.sort(mFiles, new Comparator<File>() + { + public int compare(File arg0, File arg1) + { return arg0.getName().compareToIgnoreCase(arg1.getName()); } }); - Arrays.sort(mDirs, new Comparator<File>() { - public int compare(File arg0, File arg1) { + Arrays.sort(mDirs, new Comparator<File>() + { + public int compare(File arg0, File arg1) + { return arg0.getName().compareToIgnoreCase(arg1.getName()); } }); @@ -159,8 +172,10 @@ public class ChooseDocActivity 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) { + FileObserver observer = new FileObserver(mDirectory.getPath(), FileObserver.CREATE | FileObserver.DELETE) + { + public void onEvent(int event, String path) + { mHandler.post(mUpdateFiles); } }; @@ -171,7 +186,7 @@ public class ChooseDocActivity { ChooseDocItem item = (ChooseDocItem) v.getTag(); File f = new File(item.path); - if (item.type== ChooseDocItem.Type.PARENT || item.type== ChooseDocItem.Type.DIR) + if (item.type == ChooseDocItem.Type.PARENT || item.type == ChooseDocItem.Type.DIR) { mDirectory = f; mHandler.post(mUpdateFiles); @@ -188,12 +203,14 @@ public class ChooseDocActivity } @Override - protected void onPause() { + protected void onPause() + { super.onPause(); } @Override - protected void onResume() { + protected void onResume() + { super.onResume(); // do another file scan to pick up changes to files since we were away @@ -202,23 +219,28 @@ public class ChooseDocActivity // this hides the activity @Override - public void onBackPressed() { - moveTaskToBack (true); + public void onBackPressed() + { + moveTaskToBack(true); } private void showMessage(final String title, final String body, final String okLabel) { final Activity activity = this; - runOnUiThread(new Runnable() { + runOnUiThread(new Runnable() + { @Override - public void run() { + public void run() + { new AlertDialog.Builder(activity) .setTitle(title) .setMessage(body) .setCancelable(false) - .setPositiveButton(okLabel, new DialogInterface.OnClickListener() { + .setPositiveButton(okLabel, new DialogInterface.OnClickListener() + { @Override - public void onClick(DialogInterface dialog, int which) { + public void onClick(DialogInterface dialog, int which) + { dialog.dismiss(); } }).create().show(); diff --git a/platform/android/example/app/src/main/java/com/artifex/mupdf/example/ChooseDocAdapter.java b/platform/android/example/app/src/main/java/com/artifex/mupdf/example/ChooseDocAdapter.java index 4b889a06..98d6b3ff 100644 --- a/platform/android/example/app/src/main/java/com/artifex/mupdf/example/ChooseDocAdapter.java +++ b/platform/android/example/app/src/main/java/com/artifex/mupdf/example/ChooseDocAdapter.java @@ -9,33 +9,40 @@ import android.widget.TextView; import java.util.LinkedList; -public class ChooseDocAdapter extends BaseAdapter { +public class ChooseDocAdapter extends BaseAdapter +{ private final LinkedList<ChooseDocItem> mItems; private final LayoutInflater mInflater; - public ChooseDocAdapter(LayoutInflater inflater) { + public ChooseDocAdapter(LayoutInflater inflater) + { mInflater = inflater; mItems = new LinkedList<ChooseDocItem>(); } - public void clear() { + public void clear() + { mItems.clear(); } - public void add(ChooseDocItem item) { + public void add(ChooseDocItem item) + { mItems.add(item); notifyDataSetChanged(); } - public int getCount() { + public int getCount() + { return mItems.size(); } - public Object getItem(int i) { + public Object getItem(int i) + { return null; } - public long getItemId(int arg0) { + public long getItemId(int arg0) + { return 0; } @@ -43,30 +50,34 @@ public class ChooseDocAdapter extends BaseAdapter { { switch (type) { - case PARENT: - return R.drawable.ic_explorer_up; + case PARENT: + return R.drawable.ic_explorer_up; - case DIR: - return R.drawable.ic_explorer_fldr; + case DIR: + return R.drawable.ic_explorer_fldr; - case DOC: - return R.drawable.ic_explorer_any; + case DOC: + return R.drawable.ic_explorer_any; - default: - return 0; + default: + return 0; } } - public View getView(int position, View convertView, ViewGroup parent) { + public View getView(int position, View convertView, ViewGroup parent) + { View v; - if (convertView == null) { + if (convertView == null) + { v = mInflater.inflate(R.layout.picker_entry, null); - } else { + } + else + { v = convertView; } ChooseDocItem item = mItems.get(position); - ((TextView)v.findViewById(R.id.name)).setText(item.name); - ((ImageView)v.findViewById(R.id.icon)).setImageResource(iconForType(item.type, item.name)); + ((TextView) v.findViewById(R.id.name)).setText(item.name); + ((ImageView) v.findViewById(R.id.icon)).setImageResource(iconForType(item.type, item.name)); v.setTag(item); diff --git a/platform/android/example/app/src/main/java/com/artifex/mupdf/example/ChooseDocItem.java b/platform/android/example/app/src/main/java/com/artifex/mupdf/example/ChooseDocItem.java index 194db0f3..df49e5c2 100644 --- a/platform/android/example/app/src/main/java/com/artifex/mupdf/example/ChooseDocItem.java +++ b/platform/android/example/app/src/main/java/com/artifex/mupdf/example/ChooseDocItem.java @@ -1,7 +1,9 @@ package com.artifex.mupdf.example; -public class ChooseDocItem { - public enum Type { +public class ChooseDocItem +{ + public enum Type + { PARENT, DIR, DOC } @@ -9,7 +11,8 @@ public class ChooseDocItem { final public String name; final public String path; - public ChooseDocItem(Type t, String n, String p) { + public ChooseDocItem(Type t, String n, String p) + { type = t; name = n; path = p; diff --git a/platform/android/example/app/src/main/java/com/artifex/mupdf/example/DocViewActivity.java b/platform/android/example/app/src/main/java/com/artifex/mupdf/example/DocViewActivity.java index f17bcb83..4d07c8dd 100755 --- a/platform/android/example/app/src/main/java/com/artifex/mupdf/example/DocViewActivity.java +++ b/platform/android/example/app/src/main/java/com/artifex/mupdf/example/DocViewActivity.java @@ -3,13 +3,12 @@ package com.artifex.mupdf.example; import android.app.Activity; import android.net.Uri; import android.os.Bundle; -import android.view.View; -import com.artifex.mupdf.android.DocView; +import com.artifex.mupdf.android.DocActivityView; public class DocViewActivity extends Activity { - private DocView mDocView; + private DocActivityView mDocActivityView; @Override protected void onCreate(Bundle savedInstanceState) @@ -18,18 +17,14 @@ public class DocViewActivity extends Activity // set up UI setContentView(R.layout.activity_doc_view); - mDocView = (DocView)findViewById(R.id.doc_view); + mDocActivityView = (DocActivityView) findViewById(R.id.doc_view); // get the file path Uri uri = getIntent().getData(); final String path = Uri.decode(uri.getEncodedPath()); // start the view - mDocView.start(path); - } - - public void onToggleAnnotations(View v) - { - mDocView.toggleAnnotations(); + mDocActivityView.showUI(true); // set to false for no built-in UI + mDocActivityView.start(path); } } diff --git a/platform/android/example/app/src/main/res/layout/activity_doc_view.xml b/platform/android/example/app/src/main/res/layout/activity_doc_view.xml index e21c4a04..00065cf6 100755 --- a/platform/android/example/app/src/main/res/layout/activity_doc_view.xml +++ b/platform/android/example/app/src/main/res/layout/activity_doc_view.xml @@ -4,23 +4,10 @@ android:layout_height="match_parent" android:orientation="vertical"> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - - <Button - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Annotations" - android:onClick="onToggleAnnotations"/> - - </LinearLayout> - - <com.artifex.mupdf.android.DocView + <com.artifex.mupdf.android.DocActivityView android:id="@+id/doc_view" android:layout_width="match_parent" android:layout_height="match_parent"> - </com.artifex.mupdf.android.DocView> + </com.artifex.mupdf.android.DocActivityView> </LinearLayout> diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocActivityView.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocActivityView.java new file mode 100644 index 00000000..c1bf3472 --- /dev/null +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocActivityView.java @@ -0,0 +1,343 @@ +package com.artifex.mupdf.android; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.inputmethod.InputMethodManager; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TabHost; +import android.widget.TextView; + +import com.artifex.mupdf.fitz.Link; +import com.artifex.mupdf.fitz.Outline; +import com.artifex.mupdf.fitz.R; + +public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeListener +{ + private DocView mDocView; + private DocListPagesView mDocView2; + private Context mContext = null; + private boolean layoutAgain = false; + + // tab tags + private String mTagHidden; + private String mTagFile; + private String mTagAnnotate; + private String mTagPages; + + public DocActivityView(Context context) + { + super(context); + initialize(context); + } + + public DocActivityView(Context context, AttributeSet attrs) + { + super(context, attrs); + initialize(context); + } + + public DocActivityView(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + initialize(context); + } + + protected void initialize(Context context) + { + mContext = context; + + final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + final LinearLayout view = (LinearLayout) inflater.inflate(R.layout.doc_view, null); + addView(view); + + mDocView = (DocView) view.findViewById(R.id.doc_view_inner); + + if (usePagesView()) + { + mDocView2 = new DocListPagesView(context); + mDocView2.setMainView(mDocView); + } + + if (usePagesView()) + { + // pages container + LinearLayout layout2 = (LinearLayout) findViewById(R.id.pages_container); + layout2.addView(mDocView2); + } + + // tabs + setupTabs(); + + // selection handles + View v = view.findViewById(R.id.doc_wrapper); + RelativeLayout layout = (RelativeLayout) v; + mDocView.setupHandles(layout); + + // listen for layout changes on the main doc view, and + // copy the "most visible" value to the page list. + ViewTreeObserver observer2 = mDocView.getViewTreeObserver(); + observer2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() + { + @Override + public void onGlobalLayout() + { + if (usePagesView()) + { + int mvp = mDocView.getMostVisiblePage(); + mDocView2.setMostVisiblePage(mvp); + } + } + }); + + // TODO: connect buttons to functions + } + + protected boolean usePagesView() + { + return true; + } + + protected void setupTabs() + { + TabHost tabHost = (TabHost) findViewById(R.id.tabhost); + tabHost.setup(); + + // get the tab tags. + mTagHidden = getResources().getString(R.string.hidden_tab); + mTagFile = getResources().getString(R.string.file_tab); + mTagAnnotate = getResources().getString(R.string.annotate_tab); + mTagPages = getResources().getString(R.string.pages_tab); + + // first tab is and stays hidden. + // when the search tab is selected, we programmatically "select" this hidden tab + // which results in NO tabs appearing selected in this tab host. + setupTab(tabHost, mTagHidden, R.id.hiddenTab, R.layout.tab); + tabHost.getTabWidget().getChildTabViewAt(0).setVisibility(View.GONE); + + // these tabs are shown. + setupTab(tabHost, mTagFile, R.id.fileTab, R.layout.tab_left); + setupTab(tabHost, mTagAnnotate, R.id.annotateTab, R.layout.tab); + setupTab(tabHost, mTagPages, R.id.pagesTab, R.layout.tab_right); + + // start by showing the edit tab + tabHost.setCurrentTabByTag(mTagFile); + + tabHost.setOnTabChangedListener(this); + } + + protected void setupTab(TabHost tabHost, String text, int viewId, int tabId) + { + View tabview = LayoutInflater.from(tabHost.getContext()).inflate(tabId, null); + TextView tv = (TextView) tabview.findViewById(R.id.tabText); + tv.setText(text); + + TabHost.TabSpec tab = tabHost.newTabSpec(text); + tab.setIndicator(tabview); + tab.setContent(viewId); + tabHost.addTab(tab); + } + + @Override + public void onTabChanged(String tabId) + { + // hide the search tab + findViewById(R.id.searchTab).setVisibility(View.GONE); + + // show search is not selected + showSearchSelected(false); + + // show/hide the pages view + handlePagesTab(tabId); + + hideKeyboard(); + } + + private void showSearchSelected(boolean selected) + { + + } + + protected void handlePagesTab(String tabId) + { + if (tabId.equals(mTagPages)) + showPages(); + else + hidePages(); + } + + protected void showPages() + { + LinearLayout pages = (LinearLayout) findViewById(R.id.pages_container); + if (null == pages) + return; + + if (pages.getVisibility() == View.VISIBLE) + return; + + pages.setVisibility(View.VISIBLE); + ViewTreeObserver observer = mDocView.getViewTreeObserver(); + observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() + { + @Override + public void onGlobalLayout() + { + mDocView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + mDocView.onShowPages(); + } + }); + } + + protected void hidePages() + { + LinearLayout pages = (LinearLayout) findViewById(R.id.pages_container); + if (null == pages) + return; + + if (pages.getVisibility() == View.GONE) + return; + + pages.setVisibility(View.GONE); + ViewTreeObserver observer = mDocView.getViewTreeObserver(); + observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() + { + @Override + public void onGlobalLayout() + { + mDocView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + mDocView.onHidePages(); + } + }); + } + + public boolean showKeyboard() + { + // show keyboard + InputMethodManager im = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + im.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); + + return true; + } + + public void hideKeyboard() + { + // hide the keyboard + InputMethodManager im = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + im.hideSoftInputFromWindow(mDocView.getWindowToken(), 0); + } + + public void start(final String path) + { + // wait for the first layout to finish + ViewTreeObserver observer = getViewTreeObserver(); + observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() + { + @Override + public void onGlobalLayout() + { + getViewTreeObserver().removeOnGlobalLayoutListener(this); + + // signal that we want one more layout. When the title bar get hidden, + // this view will get an additional layout, at which time we'll + // re-layout the inner view. (see below) + // I don't like this, but it seems to work. + layoutAgain = true; + mDocView.start(path); + + if (usePagesView()) + { + mDocView2.clone(mDocView); + } + + } + }); + } + + protected void onLayout(boolean changed, int left, int top, int right, int bottom) + { + + super.onLayout(changed, left, top, right, bottom); + + if (layoutAgain) + { + // this is the additional layout. + // so now re-lay out the inner view. + layoutAgain = false; + mDocView.requestLayout(); + } + } + + private void onOutline(final Outline[] outline, int level) + { + if (outline == null) + return; + + for (Outline entry : outline) + { + int numberOfSpaces = (level) * 4; + String spaces = ""; + if (numberOfSpaces > 0) + spaces = String.format("%" + numberOfSpaces + "s", " "); + Log.i("example", String.format("%d %s %s %s", entry.page + 1, spaces, entry.title, entry.uri)); + if (entry.down != null) + { + // branch + onOutline(entry.down, level + 1); + } + } + } + + private void onLinks() + { + int numPages = mDocView.getPageCount(); + for (int pageNum = 0; pageNum < numPages; pageNum++) + { + DocPageView cv = (DocPageView) mDocView.getOrCreateChild(pageNum); + + Link links[] = cv.getPage().getLinks(); + if (links != null) + { + + for (int i = 0; i < links.length; i++) + { + Link link = links[i]; + + Log.i("example", String.format("links for page %d:", pageNum)); + Log.i("example", String.format(" link %d:", i)); + Log.i("example", String.format(" page = %d", link.page)); + Log.i("example", String.format(" uri = %s", link.uri)); + Log.i("example", String.format(" bounds = %f %f %f %f ", + link.bounds.x0, link.bounds.y0, link.bounds.x1, link.bounds.y1)); + } + } + else + { + Log.i("example", String.format("no links for page %d", pageNum)); + } + + } + } + + public void showUI(boolean show) + { + View tabHost = findViewById(R.id.tabhost); + View footer = findViewById(R.id.footer); + if (show) + { + tabHost.setVisibility(View.VISIBLE); + footer.setVisibility(View.VISIBLE); + requestLayout(); + } + else + { + tabHost.setVisibility(View.GONE); + footer.setVisibility(View.GONE); + requestLayout(); + } + } +} diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocListPagesView.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocListPagesView.java new file mode 100644 index 00000000..8ee1fb5e --- /dev/null +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocListPagesView.java @@ -0,0 +1,107 @@ +package com.artifex.mupdf.android; + +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.ScaleGestureDetector; + +public class DocListPagesView extends DocViewBase +{ + + private DocViewBase mMainView; + + public DocListPagesView(Context context) + { + super(context); + } + + public DocListPagesView(Context context, AttributeSet attrs) + { + super(context, attrs); + } + + public DocListPagesView(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + } + + public void setMainView(DocViewBase v) + { + mMainView = v; + } + + @Override + protected void doSingleTap(float fx, float fy) + { + Point p = eventToScreen(fx, fy); + DocPageView v = findPageViewContainingPoint(p.x, p.y, false); + if (v != null) + { + int pageNumber = v.getPageNumber(); + mMainView.scrollToPage(pageNumber); + } + } + + @Override + protected void doDoubleTap(float fx, float fy) + { + } + + @Override + public boolean onScale(ScaleGestureDetector detector) + { + return true; + } + + @Override + protected Point constrainScrollBy(int dx, int dy) + { + // don't scroll sideways + dx = 0; + + Rect viewport = new Rect(); + getGlobalVisibleRect(viewport); + if (mPageCollectionHeight <= viewport.height()) + { + // all the pages are already visible vertically, do nothing + dy = 0; + } + else + { + int sy = getScrollY(); + + // not too far down + if (sy + dy < 0) + dy = -sy; + + // not too far up + if (mPageCollectionHeight < sy + viewport.height() + dy) + dy = 0; + } + + return new Point(dx, dy); + } + + public void setMostVisiblePage(int p) + { + // set one of the pages in the document to be the "most visible". + int numPages = getPageCount(); + for (int i = 0; i < numPages; i++) + { + DocPageView cv = (DocPageView) getOrCreateChild(i); + cv.setMostVisible(i == p); + } + } + + @Override + public void onShowPages() + { + } + + @Override + public void onHidePages() + { + } + +} diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java index 8a8d3f6f..f6a4853f 100755 --- a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java @@ -5,15 +5,16 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; import android.os.AsyncTask; +import android.support.v4.content.ContextCompat; import android.util.Log; import android.view.KeyEvent.Callback; import android.view.View; import android.view.ViewGroup; -import com.artifex.mupdf.fitz.android.AndroidDrawDevice; import com.artifex.mupdf.fitz.Annotation; import com.artifex.mupdf.fitz.Cookie; import com.artifex.mupdf.fitz.DisplayList; @@ -21,6 +22,9 @@ import com.artifex.mupdf.fitz.DisplayListDevice; import com.artifex.mupdf.fitz.Document; import com.artifex.mupdf.fitz.Matrix; import com.artifex.mupdf.fitz.Page; +import com.artifex.mupdf.fitz.R; +import com.artifex.mupdf.fitz.StructuredText; +import com.artifex.mupdf.fitz.android.AndroidDrawDevice; public class DocPageView extends View implements Callback { @@ -47,6 +51,8 @@ public class DocPageView extends View implements Callback private Rect mDisplayRect = new Rect(); private final Paint mPainter; + private final Paint mHighlightPainter; + private final Paint mDotPainter; private final Rect mSrcRect = new Rect(); private final Rect mDstRect = new Rect(); @@ -64,14 +70,32 @@ public class DocPageView extends View implements Callback public static int bitmapMarginX = 0; public static int bitmapMarginY = 0; + // use this to control whether the blue dot is drawn in the upper left corner. + private boolean isMostVisible = false; + + /// structured text, defined once at page setup time. + StructuredText mStructuredText = null; + Rect mHighlightRects[] = null; + Rect hlRect = new Rect(); + public DocPageView(Context context, Document theDoc) { super(context); setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); mDoc = theDoc; + mPainter = new Paint(); + mHighlightPainter = new Paint(); + mHighlightPainter.setColor(ContextCompat.getColor(context, R.color.text_highlight_color)); + mHighlightPainter.setStyle(Paint.Style.FILL); + mHighlightPainter.setAlpha(getContext().getResources().getInteger(R.integer.text_highlight_alpha)); + + mDotPainter = new Paint(); + mDotPainter.setStyle(Paint.Style.FILL); + mDotPainter.setColor(ContextCompat.getColor(context, R.color.blue_dot_color)); + setFocusable(true); setFocusableInTouchMode(true); } @@ -85,17 +109,19 @@ public class DocPageView extends View implements Callback mPageNum = thePageNum; // de-cache contents and annotations - if (pageContents != null) { + if (pageContents != null) + { pageContents.destroy(); pageContents = null; } - if (annotContents != null) { + if (annotContents != null) + { annotContents.destroy(); annotContents = null; } // destroy the page before making a new one. - if (mPage!=null) + if (mPage != null) mPage.destroy(); mPage = mDoc.loadPage(mPageNum); } @@ -104,33 +130,53 @@ public class DocPageView extends View implements Callback com.artifex.mupdf.fitz.Rect pageBounds = mPage.getBounds(); - float pagew = (pageBounds.x1 - pageBounds.x0)*mResolution/72f; - float pageH = (pageBounds.y1 - pageBounds.y0)*mResolution/72f; + float pagew = (pageBounds.x1 - pageBounds.x0) * mResolution / 72f; + float pageH = (pageBounds.y1 - pageBounds.y0) * mResolution / 72f; - mZoom = w/pagew; - mSize = new Point((int)(pagew*mZoom), (int)(pageH*mZoom)); + mZoom = w / pagew; + mSize = new Point((int) (pagew * mZoom), (int) (pageH * mZoom)); + + // get structured text + mStructuredText = getPage().toStructuredText(); } - public void setNewScale(float scale) { + public Page getPage() + { + return mPage; + } + + public int getPageNumber() + { + return mPageNum; + } + + public void setNewScale(float scale) + { mScale = scale; } public int getCalculatedWidth() { - return (int)(mSize.x * mScale); + return (int) (mSize.x * mScale); } public int getCalculatedHeight() { - return (int)(mSize.y * mScale); + return (int) (mSize.y * mScale); } // a test for real visibility private static final Rect visRect = new Rect(); - public boolean isReallyVisible() { + + public boolean isReallyVisible() + { return getLocalVisibleRect(visRect); } + // for clipping + private Rect clipRect = new Rect(); + private Path clipPath = new Path(); + // This function renders colored rectangles and text in place of the page. // Use it to test layouts. private void renderNoPage(Bitmap bitmap, final RenderListener listener, Rect localVisRect, Rect globalVisRect) @@ -143,7 +189,7 @@ public class DocPageView extends View implements Callback // make a rect representing the entire page in screen coordinates int[] locations = new int[2]; getLocationOnScreen(locations); - Rect pageRect = new Rect(locations[0], locations[1], locations[0]+getWidth(), locations[1]+getHeight()); + Rect pageRect = new Rect(locations[0], locations[1], locations[0] + getWidth(), locations[1] + getHeight()); // draw a yellow page with a red border containing the page number @@ -151,19 +197,19 @@ public class DocPageView extends View implements Callback Canvas c = new Canvas(bitmap); p.setColor(Color.RED); p.setStyle(Paint.Style.FILL); - c.drawRect(pageRect,p); + c.drawRect(pageRect, p); Rect smaller = new Rect(pageRect); - int inset = (int)(40*mScale); + int inset = (int) (40 * mScale); smaller.inset(inset, inset); p.setColor(Color.YELLOW); p.setStyle(Paint.Style.FILL); - c.drawRect(smaller,p); + c.drawRect(smaller, p); - String s = "" + (mPageNum+1); + String s = "" + (mPageNum + 1); p.setColor(Color.BLACK); - p.setTextSize(200.0f*mScale); - c.drawText(s, pageRect.left+(90*mScale), pageRect.top+(290*mScale), p); + p.setTextSize(200.0f * mScale); + c.drawText(s, pageRect.left + (90 * mScale), pageRect.top + (290 * mScale), p); invalidate(); listener.progress(0); @@ -176,14 +222,16 @@ public class DocPageView extends View implements Callback // get local visible rect Rect localVisRect = new Rect(); - if (!getLocalVisibleRect(localVisRect)) { + if (!getLocalVisibleRect(localVisRect)) + { listener.progress(0); return; // not visible } // get global visible rect Rect globalVisRect = new Rect(); - if (!getGlobalVisibleRect(globalVisRect)) { + if (!getGlobalVisibleRect(globalVisRect)) + { listener.progress(0); return; // not visible } @@ -191,7 +239,8 @@ public class DocPageView extends View implements Callback // do the render. if (DEBUG_PAGE_RENDERING) renderNoPage(bitmap, listener, localVisRect, globalVisRect); - else { + else + { cachePage(); renderPage(bitmap, listener, localVisRect, globalVisRect, showAnnotations); } @@ -210,32 +259,32 @@ public class DocPageView extends View implements Callback mDisplayRect.set(localVisRect); // enlarge rendering and display rects to account for available margins - int topMargin = Math.min(Math.max(globalVisRect.top -pageRect.top,0), bitmapMarginY); - int bottomMargin = Math.min(Math.max(pageRect.bottom -globalVisRect.bottom,0),bitmapMarginY); - int leftMargin = Math.min(Math.max(globalVisRect.left-pageRect.left,0), bitmapMarginX); - int rightMargin = Math.min(Math.max(pageRect.right -globalVisRect.right,0), bitmapMarginX); - - mPatchRect.top -= topMargin; - mDisplayRect.top -= topMargin; - mPatchRect.bottom += bottomMargin; + int topMargin = Math.min(Math.max(globalVisRect.top - pageRect.top, 0), bitmapMarginY); + int bottomMargin = Math.min(Math.max(pageRect.bottom - globalVisRect.bottom, 0), bitmapMarginY); + int leftMargin = Math.min(Math.max(globalVisRect.left - pageRect.left, 0), bitmapMarginX); + int rightMargin = Math.min(Math.max(pageRect.right - globalVisRect.right, 0), bitmapMarginX); + + mPatchRect.top -= topMargin; + mDisplayRect.top -= topMargin; + mPatchRect.bottom += bottomMargin; mDisplayRect.bottom += bottomMargin; - mPatchRect.left -= leftMargin; - mDisplayRect.left -= leftMargin; - mPatchRect.right += rightMargin; - mDisplayRect.right += rightMargin; + mPatchRect.left -= leftMargin; + mDisplayRect.left -= leftMargin; + mPatchRect.right += rightMargin; + mDisplayRect.right += rightMargin; // ... but clip to the bitmap Rect oldPatch = new Rect(mPatchRect); - mPatchRect.left = Math.max(mPatchRect.left, 0); - mPatchRect.top = Math.max(mPatchRect.top, 0); - mPatchRect.right = Math.min(mPatchRect.right, bitmap.getWidth()); + mPatchRect.left = Math.max(mPatchRect.left, 0); + mPatchRect.top = Math.max(mPatchRect.top, 0); + mPatchRect.right = Math.min(mPatchRect.right, bitmap.getWidth()); mPatchRect.bottom = Math.min(mPatchRect.bottom, bitmap.getHeight()); - mDisplayRect.left += (mPatchRect.left-oldPatch.left); - mDisplayRect.top += (mPatchRect.top-oldPatch.top); - mDisplayRect.right -= (mPatchRect.right-oldPatch.right); - mDisplayRect.bottom -= (mPatchRect.bottom-oldPatch.bottom); + mDisplayRect.left += (mPatchRect.left - oldPatch.left); + mDisplayRect.top += (mPatchRect.top - oldPatch.top); + mDisplayRect.right -= (mPatchRect.right - oldPatch.right); + mDisplayRect.bottom -= (mPatchRect.bottom - oldPatch.bottom); // set up the page and patch coordinates for the device int pageX0 = pageRect.left; @@ -250,7 +299,7 @@ public class DocPageView extends View implements Callback // set up a matrix for scaling Matrix ctm = Matrix.Identity(); - ctm.scale(mScale * mZoom * mResolution / 72f); + ctm.scale((float) getFactor()); // remember the final values mRenderSrcRect.set(mPatchRect); @@ -259,9 +308,11 @@ public class DocPageView extends View implements Callback mRenderBitmap = bitmap; // Render the page in the background - RenderTaskParams params = new RenderTaskParams(new RenderListener() { + RenderTaskParams params = new RenderTaskParams(new RenderListener() + { @Override - public void progress(int error) { + public void progress(int error) + { // specify where to draw to and from mDrawBitmap = mRenderBitmap; mDrawSrcRect.set(mRenderSrcRect); @@ -280,29 +331,33 @@ public class DocPageView extends View implements Callback { Cookie cookie = new Cookie(); - if (pageContents==null) + if (pageContents == null) { pageContents = new DisplayList(); DisplayListDevice dispDev = new DisplayListDevice(pageContents); - try { + try + { mPage.runPageContents(dispDev, new Matrix(1, 0, 0, 1, 0, 0), cookie); } - catch (RuntimeException e) { + catch (RuntimeException e) + { pageContents.destroy(); dispDev.destroy(); - throw(e); + throw (e); } - finally { + finally + { dispDev.destroy(); } } - if (annotContents==null) + if (annotContents == null) { // run the annotation list annotContents = new DisplayList(); DisplayListDevice annotDev = new DisplayListDevice(annotContents); - try { + try + { Annotation annotations[] = mPage.getAnnotations(); if (annotations != null) { @@ -312,24 +367,52 @@ public class DocPageView extends View implements Callback } } } - catch (RuntimeException e) { + catch (RuntimeException e) + { annotContents.destroy(); annotDev.destroy(); - throw(e); + throw (e); } - finally { + finally + { annotDev.destroy(); } } } + public void setHighlight(Point ul, Point dr) + { + // remember the rect we've been given + hlRect.set(ul.x, ul.y, dr.x, dr.y); + + // find the included text rects + com.artifex.mupdf.fitz.Rect rects[] = mStructuredText.highlight(new com.artifex.mupdf.fitz.Rect(ul.x, ul.y, dr.x, dr.y)); + if (rects == null || rects.length <= 0) + { + mHighlightRects = null; + return; + } + + // convert to Android Rects. They will be used to draw the highlights. + mHighlightRects = new Rect[rects.length]; + for (int i = 0; i < rects.length; i++) + { + mHighlightRects[i] = new Rect((int) rects[i].x0, (int) rects[i].y0, (int) rects[i].x1, (int) rects[i].y1); + } + } + + public void removeHighlight() + { + mHighlightRects = null; + } + @Override public void onDraw(Canvas canvas) { if (mFinished) return; - if (mDrawBitmap==null) + if (mDrawBitmap == null) return; // not yet rendered // set rectangles for drawing @@ -339,31 +422,78 @@ public class DocPageView extends View implements Callback // if the scale has changed, adjust the destination if (mDrawScale != mScale) { - double scale = (((double)mScale)/((double) mDrawScale)); - mDstRect.left *= scale; - mDstRect.top *= scale; - mDstRect.right *= scale; + double scale = (((double) mScale) / ((double) mDrawScale)); + mDstRect.left *= scale; + mDstRect.top *= scale; + mDstRect.right *= scale; mDstRect.bottom *= scale; } + // clip + canvas.save(); + getLocalVisibleRect(clipRect); + clipPath.reset(); + clipPath.addRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, Path.Direction.CW); + canvas.clipPath(clipPath); + // draw canvas.drawBitmap(mDrawBitmap, mSrcRect, mDstRect, mPainter); + + // highlights + if (mHighlightRects != null) + { + for (Rect r : mHighlightRects) + { + Rect r2 = pageToView(r); + canvas.drawRect(r2, mHighlightPainter); + } + } + + // draw blue dot + if (isMostVisible) + { + canvas.drawCircle(30, 30, 15, mDotPainter); + } + + canvas.restore(); } - public boolean onSingleTap(int x, int y) { - // NOTE: when double-tapping, a single-tap will also happen first. - // so that must be safe to do. - return false; + public Rect getTappedRect(Point p) + { + Point pPage = screenToPage(p.x, p.y); + com.artifex.mupdf.fitz.Rect bounds = mPage.getBounds(); + com.artifex.mupdf.fitz.Rect rects[] = + mStructuredText.highlight(new com.artifex.mupdf.fitz.Rect(bounds.x0, bounds.y0, bounds.x1, bounds.y1)); + + Rect rfound = null; + for (com.artifex.mupdf.fitz.Rect r : rects) + { + if (r.contains(pPage.x, pPage.y)) + rfound = new Rect((int) r.x0, (int) r.y0, (int) r.x1, (int) r.y1); + } + + return rfound; } - public void onDoubleTap(int x, int y) { + public String getSelectedText() + { + // convert to fitz rect + com.artifex.mupdf.fitz.Rect r = + new com.artifex.mupdf.fitz.Rect(hlRect.left, hlRect.top, hlRect.right, hlRect.bottom); + + return mStructuredText.copy(r); } - private Point screenToPage(Point p) + public Point screenToPage(Point p) { return screenToPage(p.x, p.y); } + private double getFactor() + { + return mZoom * mScale * mResolution / 72f; + } + private Point screenToPage(int screenX, int screenY) { // convert to view-relative @@ -375,29 +505,42 @@ public class DocPageView extends View implements Callback viewY -= loc[1]; // convert to page-relative - double factor = mZoom * mScale; - int pageX = (int)(((double)viewX)/factor); - int pageY = (int)(((double)viewY)/factor); + double factor = getFactor(); + + int pageX = (int) (((double) viewX) / factor); + int pageY = (int) (((double) viewY) / factor); - return new Point(pageX,pageY); + return new Point(pageX, pageY); } public Point pageToView(int pageX, int pageY) { - double factor = mZoom * mScale; + double factor = getFactor(); - int viewX = (int)(((double)pageX)*factor); - int viewY = (int)(((double)pageY)*factor); + int viewX = (int) (((double) pageX) * factor); + int viewY = (int) (((double) pageY) * factor); return new Point(viewX, viewY); } + public Rect pageToView(Rect pageR) + { + double factor = getFactor(); + + int left = (int) (((double) pageR.left) * factor); + int top = (int) (((double) pageR.top) * factor); + int right = (int) (((double) pageR.right) * factor); + int bottom = (int) (((double) pageR.bottom) * factor); + + return new Rect(left, top, right, bottom); + } + public Point viewToPage(int viewX, int viewY) { - double factor = mZoom * mScale; + double factor = getFactor(); - int pageX = (int)(((double)viewX)/factor); - int pageY = (int)(((double)viewY)/factor); + int pageX = (int) (((double) viewX) / factor); + int pageY = (int) (((double) viewY) / factor); return new Point(pageX, pageY); } @@ -407,33 +550,68 @@ public class DocPageView extends View implements Callback mFinished = true; // destroy the page - if (mPage!=null) { + if (mPage != null) + { mPage.destroy(); mPage = null; } } + public void setMostVisible(boolean val) + { + boolean wasMostVisible = isMostVisible; + isMostVisible = val; + if (isMostVisible != wasMostVisible) + { + // "most visible" has changed, so redraw. + invalidate(); + } + } + + public boolean onSingleTap(int x, int y) + { + // NOTE: when double-tapping, a single-tap will also happen first. + // so that must be safe to do. + + requestFocus(); + return false; + } + + public void onDoubleTap(int x, int y) + { + requestFocus(); + } + // during layout, a DocView-relative rect is calculated and stashed here. private final Rect mChildRect = new Rect(); - public void setChildRect(Rect r) {mChildRect.set(r);} - public Rect getChildRect() {return mChildRect;} - private class RenderTaskParams { + public void setChildRect(Rect r) + { + mChildRect.set(r); + } + + public Rect getChildRect() + { + return mChildRect; + } + + private class RenderTaskParams + { RenderTaskParams(RenderListener listener, Matrix ctm, Bitmap bitmap, int pageX0, int pageY0, int pageX1, int pageY1, int patchX0, int patchY0, int patchX1, int patchY1, boolean showAnnotations) { this.listener = listener; - this.ctm = ctm; - this.bitmap = bitmap; - this.pageX0 = pageX0; - this.pageY0 = pageY0; - this.pageX1 = pageX1; - this.pageY1 = pageY1; - this.patchX0 = patchX0; - this.patchY0 = patchY0; - this.patchX1 = patchX1; - this.patchY1 = patchY1; + this.ctm = ctm; + this.bitmap = bitmap; + this.pageX0 = pageX0; + this.pageY0 = pageY0; + this.pageX1 = pageX1; + this.pageY1 = pageY1; + this.patchX0 = patchX0; + this.patchY0 = patchY0; + this.patchX1 = patchX1; + this.patchY1 = patchY1; this.showAnnotations = showAnnotations; } @@ -457,7 +635,8 @@ public class DocPageView extends View implements Callback private RenderTaskParams params = null; @Override - protected void onPreExecute() { + protected void onPreExecute() + { super.onPreExecute(); } @@ -470,10 +649,12 @@ public class DocPageView extends View implements Callback try { Cookie cookie = new Cookie(); - if (pageContents != null) { + if (pageContents != null) + { pageContents.run(dev, params.ctm, cookie); } - if (annotContents != null && params.showAnnotations) { + if (annotContents != null && params.showAnnotations) + { annotContents.run(dev, params.ctm, cookie); } } @@ -481,7 +662,8 @@ public class DocPageView extends View implements Callback { Log.e("mupdf", e.getMessage()); } - finally { + finally + { dev.destroy(); } @@ -489,12 +671,14 @@ public class DocPageView extends View implements Callback } @Override - protected void onProgressUpdate(Void... values) { + protected void onProgressUpdate(Void... values) + { super.onProgressUpdate(values); } @Override - protected void onPostExecute(Void result) { + protected void onPostExecute(Void result) + { super.onPostExecute(result); params.listener.progress(0); } diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java index 5e529ed4..71c0fbe6 100755..100644 --- a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java @@ -1,933 +1,291 @@ package com.artifex.mupdf.android; + import android.content.Context; -import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.RectF; -import android.os.Handler; import android.util.AttributeSet; import android.util.DisplayMetrics; -import android.util.SparseArray; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; +import android.util.Log; +import android.util.TypedValue; import android.view.View; -import android.view.ViewTreeObserver; -import android.view.WindowManager; -import android.widget.Adapter; -import android.widget.AdapterView; -import android.widget.Scroller; +import android.widget.RelativeLayout; -import com.artifex.mupdf.fitz.Document; +import com.artifex.mupdf.fitz.R; -public class DocView - extends AdapterView<Adapter> - implements GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener, Runnable +public class DocView extends DocViewBase implements DragHandleListener { - private static final int GAP = 20; - - private static final float MIN_SCALE = .15f; - private static final float MAX_SCALE = 5.0f; - - private PageAdapter mAdapter; - private boolean mFinished = false; - - private final SparseArray<View> mChildViews = new SparseArray<View>(3); - - 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 GestureDetector mGestureDetector; - private ScaleGestureDetector mScaleGestureDetector; - - // bitmaps for rendering - // these are created by the activity and set using setBitmaps() - private final static double OVERSIZE_FACTOR = 1.3; - private final Bitmap[] bitmaps = {null,null}; - - private int bitmapIndex = 0; - private boolean renderRequested = false; - private int renderCount = 0; - - // used during layout - private final Rect mChildRect = new Rect(); - private final Rect mViewport = new Rect(); - private final Point mViewportOrigin = new Point(); - private final Rect mBlockRect = new Rect(); - private final Rect mLastBlockRect = new Rect(); - private int mLastLayoutColumns = 1; - protected int mPageCollectionHeight; - private int mPageCollectionWidth; - - // for flinging - 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 float MIN_FLING_VELOCITY = 1500.0f; - private static final long FLING_THROTTLE_TIME = 20; - - private Scroller mScroller; - private Stepper mStepper; - private int mScrollerLastX; - private int mScrollerLastY; - private long mFlingStartTime; - - // for single- and double-tapping - private long mLastTapTime = 0; - private float lastTapX; - private float lastTapY; - private int mTapStatus = 0; - - private static final int DOUBLE_TAP_TIME = 300; - private static final int SHOW_KEYBOARD_TIME = 500; - - // the document. - private Document mDoc; - - private boolean mStarted = false; - - public DocView(Context context) { + // selection handles + private DragHandle mSelectionHandleTopLeft = null; + private DragHandle mSelectionHandleBottomRight = null; + + // dot size and padding + private int selectionHandlePadPx; + private int selectionHandleSizePx; + + // selection + DocPageView selectionStartPage = null; + Point selectionStartLoc = new Point(); + DocPageView selectionEndPage = null; + Point selectionEndLoc = new Point(); + + public DocView(Context context) + { super(context); initialize(context); } - public DocView(Context context, AttributeSet attrs) { + public DocView(Context context, AttributeSet attrs) + { super(context, attrs); initialize(context); } - public DocView(Context context, AttributeSet attrs, int defStyle) { + public DocView(Context context, AttributeSet attrs, int defStyle) + { super(context, attrs, defStyle); initialize(context); } - protected Context mContext = null; - - protected void initialize(Context context) + private void initialize(Context context) { - mContext = context; - mGestureDetector = new GestureDetector(context, this); - mScaleGestureDetector = new ScaleGestureDetector(context, this); - mScroller = new Scroller(context); - mStepper = new Stepper(this, this); - - this.setClipChildren(false); + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); - // create bitmaps - makeBitmaps(); - } + int padDp = context.getResources().getInteger(R.integer.selection_dot_padding); + int sizeDp = context.getResources().getInteger(R.integer.selection_dot_size); - private void makeBitmaps() - { - // get current screen size - WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - DisplayMetrics metrics = new DisplayMetrics(); - wm.getDefaultDisplay().getMetrics(metrics); - int screenW = metrics.widthPixels; - int screenH = metrics.heightPixels; - - // make two bitmaps. - // make them large enough for both screen orientations, so we don't have to - // change them when the orientation changes. - - int w = (int)(screenW*OVERSIZE_FACTOR); - int h = (int)(screenH*OVERSIZE_FACTOR); - int size = Math.max(w,h); - for (int i=0;i<bitmaps.length;i++) - bitmaps[i] = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - - DocPageView.bitmapMarginX = (w-screenW)/2; - DocPageView.bitmapMarginY = (h-screenH)/2; - } - - public void start(final String path) - { - // wait for the layout to finish - ViewTreeObserver observer = getViewTreeObserver(); - observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() - { - @Override - public void onGlobalLayout() - { - getViewTreeObserver().removeOnGlobalLayoutListener(this); - - mAdapter = new PageAdapter(mContext); - mAdapter.setWidth(getWidth()); - mDoc = new Document(path); - mAdapter.setDocument(mDoc); - mScale = 1.0f; - mStarted = true; - triggerRender(); - } - }); - } + TypedValue outValue = new TypedValue(); + getResources().getValue(R.dimen.selection_dot_scale, outValue, true); + float scale = outValue.getFloat(); - private void onScaleChild(View v, Float scale) - { - ((DocPageView)v).setNewScale(scale); + selectionHandlePadPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, padDp, metrics); + selectionHandleSizePx = (int) (scale * TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, sizeDp, metrics)); + selectionHandleSizePx = selectionHandleSizePx * 8 / 10; } - public void onOrientationChange() + // create the selection handles + public void setupHandles(RelativeLayout layout) { - triggerRender(); + // selection handles + mSelectionHandleTopLeft = setupHandle(layout, DragHandle.SELECTION_TOP_LEFT); + mSelectionHandleBottomRight = setupHandle(layout, DragHandle.SELECTION_BOTTOM_RIGHT); } - private void onSizeChange(float factor) + // create a single DragHandle of a particular kind + private DragHandle setupHandle(RelativeLayout layout, int kind) { - mScale *= factor; - scaleChildren(); - requestLayout(); - } - - public boolean onDown(MotionEvent arg0) { - mScroller.forceFinished(true); - return true; - } - - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - - // not while we're scaling - if (mScaling) - return true; - - // not while a previous fling is underway - if (!mScroller.isFinished()) - return true; - - // must be really flinging - float vel = Math.max(Math.abs(velocityX),Math.abs(velocityY)); - if (vel<MIN_FLING_VELOCITY) - return false; - - // what direction? - int direction = directionOfTravel(velocityX,velocityY); - - mFlingStartTime = System.currentTimeMillis(); - - switch (direction) - { - case MOVING_DOWN: - smoothScrollBy(0, getHeight()/2); - break; - - case MOVING_UP: - smoothScrollBy(0, -getHeight()/2); - break; - - default: - break; - } - - return true; - } - - 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; + // create + DragHandle dh; + if (kind == DragHandle.DRAG) + dh = new DragHandle(getContext(), R.layout.drag_handle, kind); + else if (kind == DragHandle.ROTATE) + dh = new DragHandle(getContext(), R.layout.rotate_handle, kind); else - return MOVING_DIAGONALLY; - } - - public void onLongPress(MotionEvent e) { - } - - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - - // not if we're scaling - if (mScaling) - return true; - - // not if a previous fling is underway - if (!mScroller.isFinished()) - return true; + dh = new DragHandle(getContext(), R.layout.resize_handle, kind); - // accumulate scrolling amount. - mXScroll -= distanceX; - mYScroll -= distanceY; + // add to the layout, initially hidden + layout.addView(dh); + dh.show(false); - requestLayout(); + // establish the listener + dh.setDragHandleListener(this); - return true; + return dh; } - public void onShowPress(MotionEvent e) { - } - - protected DocPageView findPageViewContainingPoint(int x, int y, boolean includeMargin) + // show or hide the selection handles + private void showSelectionHandles(boolean show) { - for (int i = 0; i < getChildCount(); i++) - { - // get the rect for the page - View child = getChildAt(i); - Rect childRect = new Rect(); - child.getGlobalVisibleRect(childRect); - - // add in the margin - if (includeMargin) - { - childRect.left -= GAP/2; - childRect.right += GAP/2; - childRect.top -= GAP/2; - childRect.bottom += GAP/2; - } - - // see if the rect contains the point - if (childRect.contains(x,y)) - return (DocPageView)child; - } - - return null; + mSelectionHandleTopLeft.show(show); + mSelectionHandleBottomRight.show(show); } - protected Point eventToScreen(float fx, float fy) + private boolean getSelectionHandlesVisible() { - int x = Math.round(fx); - int y = Math.round(fy); - Rect docRect = new Rect(); - getGlobalVisibleRect(docRect); - x += docRect.left; - y += docRect.top; - - return new Point(x,y); + return (mSelectionHandleTopLeft.getVisibility() == View.VISIBLE); } + @Override protected void doSingleTap(float fx, float fy) { // find the page view that was tapped. - Point p = eventToScreen(fx,fy); + Point p = eventToScreen(fx, fy); final DocPageView dpv = findPageViewContainingPoint(p.x, p.y, false); - if (dpv==null) + if (dpv == null) return; - // see if the age wants to handle the single tap - boolean handled = dpv.onSingleTap(p.x,p.y); - - // if not, ... - if (!handled) + if (getSelectionHandlesVisible()) { - // schedule a task in the near future to check if we're still a single-tap. - final Handler handler = new Handler(); - final Point tappedPoint = p; - handler.postDelayed(new Runnable() + // hide handles and remove selection from pages + showSelectionHandles(false); + int numPages = getPageCount(); + for (int i = 0; i < numPages; i++) { - @Override - public void run() - { - if (mTapStatus==1) - { - // still single - } - else - { - // double - } - mTapStatus = 0; - } - }, SHOW_KEYBOARD_TIME); - } - } - - protected void doDoubleTap(float fx, float fy) - { - Point p = eventToScreen(fx,fy); - DocPageView v = findPageViewContainingPoint(p.x, p.y, false); - if (v != null) { - v.onDoubleTap(p.x,p.y); - } - } - - public boolean onSingleTapUp(final MotionEvent e) - { - long now = System.currentTimeMillis(); - if (mLastTapTime!=0 && ((now-mLastTapTime)<DOUBLE_TAP_TIME)) - { - mTapStatus = 2; - doDoubleTap(lastTapX,lastTapY); - mLastTapTime = 0; + DocPageView cv = (DocPageView) getOrCreateChild(i); + cv.removeHighlight(); + if (cv.isReallyVisible()) + cv.invalidate(); + } } else { - mLastTapTime = now; - lastTapX = e.getX(); - lastTapY = e.getY(); - doSingleTap(lastTapX, lastTapY); - mTapStatus = 1; - } - - return false; - } - - private void scaleChildren() - { - // scale children - for (int i=0; i<getPageCount(); i++) - { - DocPageView cv = (DocPageView)getOrCreateChild(i); - cv.setNewScale(mScale); - } - } - - public boolean onScale(ScaleGestureDetector detector) - { - // new scale factor - float previousScale = mScale; - mScale = Math.min(Math.max(mScale * detector.getScaleFactor(), MIN_SCALE), MAX_SCALE); - - // did we really scale? - if (mScale == previousScale) - return true; - - // scale children - scaleChildren(); - - // maintain focus while scaling - double scale = mScale/previousScale; - double currentFocusX = detector.getFocusX(); - double currentFocusY = detector.getFocusY(); - double viewFocusX = (int)currentFocusX + getScrollX(); - double viewFocusY = (int)currentFocusY + getScrollY(); - int diffX = (int)(viewFocusX * (1-scale)); - int diffY = (int)(viewFocusY * (1-scale)); - mXScroll += diffX; - mYScroll += diffY; - - 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; - - return true; - } - - public void onScaleEnd(ScaleGestureDetector detector) - { - // When a pinch-scale is done, we want to get n-across - // to fit properly. - - // get current viewport - Rect viewport = new Rect(); - getGlobalVisibleRect(viewport); - - // if we're at one column and wider than the viewport, - // leave it alone. - if (mLastLayoutColumns==0 && mPageCollectionWidth>=viewport.width()) - { - mScaling = false; - return; - } - - // ratio of the viewport width to layout width - float ratio = ((float)(viewport.width()))/((float)(mPageCollectionWidth)); - - // set a new scale factor - mScale *= ratio; - scaleChildren(); - - // scroll so the left edged is flush to the viewport. - mXScroll +=getScrollX(); - - requestLayout(); + // point in screen coordinates, result in page coordinates + Rect r = dpv.getTappedRect(p); + if (r != null) + { + // show handles + showSelectionHandles(true); - mScaling = false; - } + // set highlight boundaries + selectionStartPage = dpv; + selectionStartLoc.set(r.left, r.top); + selectionEndPage = dpv; + selectionEndLoc.set(r.right, r.bottom); - @Override - public boolean onTouchEvent(MotionEvent event) { + // do highlight + doHighlight(); - if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { - // do something when user interaction begins - } - - if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) { - // do something when user interaction ends - triggerRender(); + moveHandlesToCorners(); + } } - - mScaleGestureDetector.onTouchEvent(event); - mGestureDetector.onTouchEvent(event); - - return true; } - protected int getPageCount() + private void doHighlight() { - return getAdapter().getCount(); - } - - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - - super.onLayout(changed, left, top, right, bottom); - - if (!mStarted) - return; - - // not if there are no pages - if (getPageCount()==0) - return; - - int numDocPages = getPageCount(); - - // not if we've been finished - if (finished()) - return; - - // perform any pending scrolling - scrollBy(-mXScroll, -mYScroll); - mXScroll = mYScroll = 0; - - // get current viewport - mViewportOrigin.set(getScrollX(), getScrollY()); - getGlobalVisibleRect(mViewport); - mViewport.offsetTo(mViewportOrigin.x, mViewportOrigin.y); - - // find the widest child - int maxw = 0; - for (int i=0; i<getPageCount(); i++) - { - DocPageView cv = (DocPageView)getOrCreateChild(i); - - int childWidth = cv.getCalculatedWidth(); - if (childWidth>maxw) - maxw = childWidth; - } - - // how many columns? - double dcol = (double)(mViewport.width()+GAP)/(double)(maxw+GAP); - int columns = (int) dcol; - - // lay them out - int mostVisibleChildHeight = -1; - int childTop = 0; - mPageCollectionHeight = 0; - mPageCollectionWidth = 0; - int column = 0; - mBlockRect.setEmpty(); - - for (int i=0; i<getPageCount(); i++) + // TODO: for now, we're dealing with one page at a time + int numPages = getPageCount(); + for (int i = 0; i < numPages; i++) { - DocPageView cv = (DocPageView)getOrCreateChild(i); - int childWidth = cv.getCalculatedWidth(); - int childHeight = cv.getCalculatedHeight(); - int childLeft = column * (maxw + GAP); - int childRight = childLeft + childWidth; - int childBottom = childTop + childHeight; - mChildRect.set(childLeft, childTop, childRight, childBottom); - - // stash the rect in the page view for later use. - cv.setChildRect(mChildRect); - - // at each layout, we remember the entire width and height of the laid-out - // pages. This is used in applying constraints to scrolling amounts. - if (childBottom> mPageCollectionHeight) - mPageCollectionHeight = childBottom; - if (childRight>mPageCollectionWidth) - mPageCollectionWidth = childRight; - - if (mBlockRect.isEmpty()) - mBlockRect.set(mChildRect); - else - mBlockRect.union(mChildRect); - - if (mChildRect.intersect(mViewport) && i<numDocPages) + DocPageView cv = (DocPageView) getOrCreateChild(i); + if (cv.isReallyVisible() && cv == selectionStartPage && cv == selectionEndPage) { - // visible, so include in layout - if (cv.getParent()==null) - addChildToLayout(cv); - cv.layout(childLeft, childTop, childRight, childBottom); - cv.invalidate(); + cv.setHighlight(selectionStartLoc, selectionEndLoc); + logSelectedText(); } else { - // not visible, so remove from layout - removeViewInLayout(cv); + cv.removeHighlight(); } - - column++; - if (column >= columns) { - column = 0; - childTop += childHeight; - childTop += GAP; - } - } - - // if the number of columns has changed, do some scrolling to adjust - if (mScaling && columns>=1 && mLastLayoutColumns>=1 && mLastLayoutColumns!=columns) - { - // x - center in the viewport - int dx = mBlockRect.centerX() - mViewport.centerX(); - scrollBy(dx,0); - - // y - attempt to keep what's in the center of the viewport in view. - int oldy = mViewport.centerY() - mLastBlockRect.top; - int newy = (int)((float)oldy*mBlockRect.height()/mLastBlockRect.height()); - scrollBy(0,newy-oldy); - } - mLastLayoutColumns = columns; - mLastBlockRect.set(mBlockRect); - - // see if we're handling a start page - handleStartPage(); - - triggerRender(); - } - - // start page, get and set. - private int mStartPage = 0; - public void setStartPage(int page) { - mStartPage = page; - } - protected int getStartPage() {return mStartPage;} - - // handle start page - public void handleStartPage() - { - // if we've been given a start page, go there. - final int start = getStartPage(); - if (start>0) - { - setStartPage(0); // but just once - - // post all of this so that we get an additional layout request - final Handler handler = new Handler(); - handler.post(new Runnable() { - @Override - public void run() { - DocPageView cv = (DocPageView)getOrCreateChild(start-1); - Rect r = cv.getChildRect(); - scrollBy(0,r.top); - requestLayout(); - } - }); + cv.invalidate(); } } - // override the view's scrollBy() function so we can - // take the opportunity to apply some constraints - @Override - public void scrollBy(int dx, int dy) - { - Point p = constrainScrollBy(dx, dy); - super.scrollBy(p.x, p.y); - } - - // apply contraints to every scroll request. - - protected Point constrainScrollBy(int dx, int dy) + protected void doDoubleTap(float fx, float fy) { - int vph; - int vpw; - { - Rect viewport = new Rect(); - getGlobalVisibleRect(viewport); - vph = viewport.height(); - vpw = viewport.width(); - } - int sx = getScrollX(); - int sy = getScrollY(); - - if (mPageCollectionWidth <= vpw) - { - // not too far to the right - if (mPageCollectionWidth-sx-dx > vpw) - dx = 0; - - // not too far to the left - if (sx+dx>0) - dx = -sx; - } - else - { - // not too far to the right - if (mPageCollectionWidth < sx+vpw+dx) - dx = 0; - - // not too far to the left - if (sx+dx < 0) - dx = -sx; - } - - if (mPageCollectionHeight <= vph) - { - // not too far down - if (mPageCollectionHeight-sy-dy > vph) - dy = 0; - // not too far up - if (sy+dy>0) - dy = -sy; - } - else - { - // not too far down - if (sy+dy < 0) - dy = -sy; - - // not too far up. - if (mPageCollectionHeight+2*vph/3 < sy+vph+dy) - dy = 0; - } - - return new Point(dx, dy); } - @Override - public Adapter getAdapter() { - return mAdapter; - } + private Point viewToScreen(Point p) + { + Point newPoint = new Point(p); - @Override - public View getSelectedView() { - return null; - } + Rect r = new Rect(); + this.getGlobalVisibleRect(r); - @Override - public void setAdapter(Adapter adapter) { - mAdapter = (PageAdapter)adapter; - requestLayout(); - } + newPoint.offset(r.left, r.top); - @Override - public void setSelection(int arg0) { - throw new UnsupportedOperationException("setSelection is not supported"); + return newPoint; } - private View getCached() { - return null; - } + // position a handle, given page coordinates + protected void positionHandle(DragHandle handle, DocPageView dpv, int pageX, int pageY) + { + if (handle != null) + { + // convert to DocPageView-based coords + Point p = dpv.pageToView(pageX, pageY); - protected View getOrCreateChild(int i) { - View v = mChildViews.get(i); - if (v == null) { - v = getViewFromAdapter(i); - mChildViews.append(i, v); // Record the view against it's adapter index - onScaleChild(v, mScale); - } + // offset to 0,0 + p.offset(dpv.getLeft(), dpv.getTop()); - return v; - } + // offset to position in the scrolling view (this) + p.offset(-getScrollX(), -getScrollY()); - protected View getViewFromAdapter(int index) - { - return getAdapter().getView(index, getCached(), this); - } + // offset based on handle size and padding + p.offset(-selectionHandlePadPx - selectionHandleSizePx / 2, -selectionHandlePadPx - selectionHandleSizePx / 2); - private void addChildToLayout(View v) { - LayoutParams params = v.getLayoutParams(); - if (params == null) { - params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + // move it + handle.moveTo(p.x, p.y); } - addViewInLayout(v, 0, params, true); - } - - private void triggerRender() - { - // Note that a render is needed - renderRequested = true; - - // If the previous render has completed, start a new one. Otherwise - // a new one will start as soon as the previous one completes. - if (renderCount == 0) - renderPages(); } - private void renderPages() + @Override + public void onStartDrag(DragHandle handle) { - renderRequested = false; - - if (mFinished) - return; - - if (bitmaps == null) - return; - // Rotate to the next bitmap - bitmapIndex++; - if (bitmapIndex>=bitmaps.length) - bitmapIndex = 0; - - // iterate through the children - for (int i = 0; i < getPageCount(); i++) { - - if (mFinished) - return; - - final DocPageView cv = (DocPageView) getOrCreateChild(i); - if (cv.getParent()!=null && cv.isReallyVisible()) { - // Count up as we kick off rendering of each visible page - renderCount++; - cv.render(bitmaps[bitmapIndex], new RenderListener() { - @Override - public void progress(int error) { - - if (error==0) - cv.invalidate(); - - // Count down as they complete - renderCount--; - - if (renderCount==0) { - if (renderRequested) { - // If this phase of rendering has completed and another has - // been requested, start it now - renderPages(); - } - } - } - }, mShowAnnotations); - } - } } @Override - public void run() + public void onDrag(DragHandle handle) { - if (!mScroller.isFinished()) + if (handle == mSelectionHandleTopLeft) { - mScroller.computeScrollOffset(); - int x = mScroller.getCurrX(); - int y = mScroller.getCurrY(); - mXScroll += x - mScrollerLastX; - mYScroll += y - mScrollerLastY; - mScrollerLastX = x; - mScrollerLastY = y; - - // limit the amount of repeated layouts. - long tNow = System.currentTimeMillis(); - long diff = tNow - mFlingStartTime; - if (diff>FLING_THROTTLE_TIME) + Point p1 = mSelectionHandleTopLeft.getPosition(); + p1.offset(selectionHandlePadPx + selectionHandleSizePx / 2, selectionHandlePadPx + selectionHandleSizePx / 2); + p1 = viewToScreen(p1); + DocPageView pageView1 = findPageViewContainingPoint(p1.x, p1.y, false); + if (pageView1 != null) { - requestLayout(); - mFlingStartTime = tNow; + selectionStartPage = pageView1; + p1 = pageView1.screenToPage(p1); + selectionStartLoc.set(p1.x, p1.y); } - - mStepper.prod(); - } - else - { - // one more - long tNow = System.currentTimeMillis(); - if (tNow != mFlingStartTime) - requestLayout(); } - } - - public void finish() - { - // we're done with this view. - mFinished = true; - // first, hide and remove all the children - for (int i=0; i<getPageCount(); i++) + if (handle == mSelectionHandleBottomRight) { - DocPageView cv = (DocPageView)getOrCreateChild(i); - cv.setVisibility(GONE); - removeViewInLayout(cv); - cv.finish(); + Point p2 = mSelectionHandleBottomRight.getPosition(); + p2.offset(selectionHandlePadPx + selectionHandleSizePx / 2, selectionHandlePadPx + selectionHandleSizePx / 2); + p2 = viewToScreen(p2); + DocPageView pageView2 = findPageViewContainingPoint(p2.x, p2.y, false); + if (pageView2 != null) + { + selectionEndPage = pageView2; + p2 = pageView2.screenToPage(p2); + selectionEndLoc.set(p2.x, p2.y); + } } - } - public boolean finished() {return mFinished;} + doHighlight(); + } - protected void smoothScrollBy(int dx, int dy) + @Override + public void onEndDrag(DragHandle handle) { - mScrollerLastX = mScrollerLastY = 0; - mScroller.startScroll(0, 0, dx, dy, 400); - mStepper.prod(); + moveHandlesToCorners(); + logSelectedText(); } - public void scrollToPage(int pageNumber) + private void logSelectedText() { - // scroll to bring the page into view - - // get current viewport - Rect viewport = new Rect(); - getGlobalVisibleRect(viewport); - - // offset it based on current scroll position - Point viewportOrigin = new Point(); - viewportOrigin.set(getScrollX(), getScrollY()); - viewport.offsetTo(viewportOrigin.x, viewportOrigin.y); - - // get page rect from last layout - DocPageView cv = (DocPageView)getOrCreateChild(pageNumber); - Rect childRect = cv.getChildRect(); - - // scroll - if ((childRect.height()) > viewport.height()) + int numPages = getPageCount(); + for (int i = 0; i < numPages; i++) { - // put the top of the page at the top and the left at 0 - smoothScrollBy(getScrollX(),getScrollY()-childRect.top); - } - else - { - // if the whole page is not visible, move the center of the page at the center - if (childRect.top < viewport.top || childRect.bottom > viewport.bottom) + DocPageView cv = (DocPageView) getOrCreateChild(i); + String s = cv.getSelectedText(); + if (s != null) { - if (childRect.top==0) - smoothScrollBy(0, getScrollY()); - else - smoothScrollBy(0, getScrollY() + viewport.height() / 2 - (childRect.bottom + childRect.top) / 2); + Log.i("example", s); } } } - private Point viewToScreen(Point p) + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - Point newPoint = new Point(p); - - Rect r = new Rect(); - this.getGlobalVisibleRect(r); - newPoint.offset(r.left, r.top); + super.onLayout(changed, left, top, right, bottom); - return newPoint; + moveHandlesToCorners(); } - public void scrollBoxIntoView (int pageNum, RectF box) + private void moveHandlesToCorners() { - // get our viewport - Rect viewport = new Rect(); - getGlobalVisibleRect(viewport); - viewport.offset(0,-viewport.top); - - // get the location of the box's lower left corner, - // relative to the viewport - DocPageView cv = (DocPageView)getOrCreateChild(pageNum); - Point point = cv.pageToView((int)box.left,(int)box.bottom); - Rect childRect = cv.getChildRect(); - point.y += childRect.top; - point.y -= getScrollY(); - - // if the point is outside the viewport, scroll so it is. - if (point.y<viewport.top || point.y>viewport.bottom) + if (selectionStartPage != null && selectionEndPage != null) { - int diff = (viewport.top + viewport.bottom)/2 - point.y; - smoothScrollBy(0,diff); + positionHandle(mSelectionHandleTopLeft, selectionStartPage, selectionStartLoc.x, selectionStartLoc.y); + positionHandle(mSelectionHandleBottomRight, selectionEndPage, selectionEndLoc.x, selectionEndLoc.y); } } - - private boolean mShowAnnotations = false; - public void toggleAnnotations() - { - mShowAnnotations = !mShowAnnotations; - triggerRender(); - } } diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocViewBase.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocViewBase.java new file mode 100755 index 00000000..23f60026 --- /dev/null +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocViewBase.java @@ -0,0 +1,1030 @@ +package com.artifex.mupdf.android; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.Handler; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.SparseArray; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; +import android.view.WindowManager; +import android.widget.Adapter; +import android.widget.AdapterView; +import android.widget.Scroller; + +import com.artifex.mupdf.fitz.Document; +import com.artifex.mupdf.fitz.R; + +public class DocViewBase + extends AdapterView<Adapter> + implements GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener, Runnable +{ + private static final int GAP = 20; + + private static final float MIN_SCALE = .15f; + private static final float MAX_SCALE = 5.0f; + + private PageAdapter mAdapter; + private boolean mFinished = false; + + private final SparseArray<View> mChildViews = new SparseArray<View>(3); + + 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 GestureDetector mGestureDetector; + private ScaleGestureDetector mScaleGestureDetector; + + // bitmaps for rendering + // these are created by the activity and set using setBitmaps() + private final static double OVERSIZE_FACTOR = 1.3; + private final Bitmap[] bitmaps = {null, null}; + + private int bitmapIndex = 0; + private boolean renderRequested = false; + private int renderCount = 0; + + // used during layout + private final Rect mChildRect = new Rect(); + private final Rect mViewport = new Rect(); + private final Point mViewportOrigin = new Point(); + private final Rect mBlockRect = new Rect(); + private final Rect mLastBlockRect = new Rect(); + private int mLastLayoutColumns = 1; + protected int mPageCollectionHeight; + private int mPageCollectionWidth; + + // for flinging + 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 float MIN_FLING_VELOCITY = 1500.0f; + private static final long FLING_THROTTLE_TIME = 20; + + private Scroller mScroller; + private Stepper mStepper; + private int mScrollerLastX; + private int mScrollerLastY; + private long mFlingStartTime; + + // for single- and double-tapping + private long mLastTapTime = 0; + private float lastTapX; + private float lastTapY; + private int mTapStatus = 0; + + private static final int DOUBLE_TAP_TIME = 300; + private static final int SHOW_KEYBOARD_TIME = 500; + + // the document. + private Document mDoc; + + private boolean mStarted = false; + + private boolean mShowAnnotations = false; + + // on each layout, compute the page that has the most visible height. + // That's considered to be the "current" page for purposes of drawing the blue dot + // on the pages list. + private int mostVisibleChild = -1; + private final Rect mostVisibleRect = new Rect(); + + public DocViewBase(Context context) + { + super(context); + initialize(context); + } + + public DocViewBase(Context context, AttributeSet attrs) + { + super(context, attrs); + initialize(context); + } + + public DocViewBase(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + initialize(context); + } + + protected Context mContext = null; + + private void initialize(Context context) + { + mContext = context; + mGestureDetector = new GestureDetector(context, this); + mScaleGestureDetector = new ScaleGestureDetector(context, this); + mScroller = new Scroller(context); + mStepper = new Stepper(this, this); + + makeBitmaps(); + } + + private void makeBitmaps() + { + // get current screen size + WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics metrics = new DisplayMetrics(); + wm.getDefaultDisplay().getMetrics(metrics); + int screenW = metrics.widthPixels; + int screenH = metrics.heightPixels; + + // make two bitmaps. + // make them large enough for both screen orientations, so we don't have to + // change them when the orientation changes. + + int w = (int) (screenW * OVERSIZE_FACTOR); + int h = (int) (screenH * OVERSIZE_FACTOR); + int size = Math.max(w, h); + for (int i = 0; i < bitmaps.length; i++) + bitmaps[i] = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + + DocPageView.bitmapMarginX = (w - screenW) / 2; + DocPageView.bitmapMarginY = (h - screenH) / 2; + } + + public void start(final String path) + { + mAdapter = new PageAdapter(mContext); + mAdapter.setWidth(getWidth()); + mDoc = new Document(path); + mAdapter.setDocument(mDoc); + mScale = 1.0f; + mStarted = true; + triggerRender(); + } + + public void clone(final DocViewBase docView) + { + mAdapter = new PageAdapter(mContext); + mAdapter.setWidth(docView.getWidth()); + mDoc = docView.getDoc(); + mAdapter.setDocument(mDoc); + + int pagelist_width_percentage = getContext().getResources().getInteger(R.integer.pagelist_width_percentage); + mScale = ((float) pagelist_width_percentage) / 100f; + + mStarted = true; + triggerRender(); + } + + public Document getDoc() + { + return mDoc; + } + + private void onScaleChild(View v, Float scale) + { + ((DocPageView) v).setNewScale(scale); + } + + public void onOrientationChange() + { + triggerRender(); + } + + private void onSizeChange(float factor) + { + mScale *= factor; + scaleChildren(); + requestLayout(); + } + + public boolean onDown(MotionEvent arg0) + { + mScroller.forceFinished(true); + return true; + } + + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) + { + + // not while we're scaling + if (mScaling) + return true; + + // not while a previous fling is underway + if (!mScroller.isFinished()) + return true; + + // must be really flinging + float vel = Math.max(Math.abs(velocityX), Math.abs(velocityY)); + if (vel < MIN_FLING_VELOCITY) + return false; + + // what direction? + int direction = directionOfTravel(velocityX, velocityY); + + mFlingStartTime = System.currentTimeMillis(); + + switch (direction) + { + case MOVING_DOWN: + smoothScrollBy(0, getHeight() / 2); + break; + + case MOVING_UP: + smoothScrollBy(0, -getHeight() / 2); + break; + + default: + break; + } + + return true; + } + + public void setScale(float val) + { + mScale = val; + } + + 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; + } + + public void onLongPress(MotionEvent e) + { + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) + { + + // not if we're scaling + if (mScaling) + return true; + + // not if a previous fling is underway + if (!mScroller.isFinished()) + return true; + + // accumulate scrolling amount. + mXScroll -= distanceX; + mYScroll -= distanceY; + + requestLayout(); + + return true; + } + + public void onShowPress(MotionEvent e) + { + } + + protected DocPageView findPageViewContainingPoint(int x, int y, boolean includeMargin) + { + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) + { + // get the rect for the page + View child = getChildAt(i); + Rect childRect = new Rect(); + child.getGlobalVisibleRect(childRect); + + // add in the margin + if (includeMargin) + { + childRect.left -= GAP / 2; + childRect.right += GAP / 2; + childRect.top -= GAP / 2; + childRect.bottom += GAP / 2; + } + + // see if the rect contains the point + if (childRect.contains(x, y)) + return (DocPageView) child; + } + + return null; + } + + protected Point eventToScreen(float fx, float fy) + { + int x = Math.round(fx); + int y = Math.round(fy); + Rect docRect = new Rect(); + getGlobalVisibleRect(docRect); + x += docRect.left; + y += docRect.top; + + return new Point(x, y); + } + + protected void doSingleTap(float fx, float fy) + { + // find the page view that was tapped. + Point p = eventToScreen(fx, fy); + final DocPageView dpv = findPageViewContainingPoint(p.x, p.y, false); + if (dpv == null) + return; + + // see if the age wants to handle the single tap + boolean handled = dpv.onSingleTap(p.x, p.y); + + // if not, ... + if (!handled) + { + // schedule a task in the near future to check if we're still a single-tap. + final Handler handler = new Handler(); + final Point tappedPoint = p; + handler.postDelayed(new Runnable() + { + @Override + public void run() + { + if (mTapStatus == 1) + { + // still single + } + else + { + // double + } + mTapStatus = 0; + } + }, SHOW_KEYBOARD_TIME); + } + } + + protected void doDoubleTap(float fx, float fy) + { + Point p = eventToScreen(fx, fy); + DocPageView v = findPageViewContainingPoint(p.x, p.y, false); + if (v != null) + { + v.onDoubleTap(p.x, p.y); + } + } + + public boolean onSingleTapUp(final MotionEvent e) + { + long now = System.currentTimeMillis(); + if (mLastTapTime != 0 && ((now - mLastTapTime) < DOUBLE_TAP_TIME)) + { + mTapStatus = 2; + doDoubleTap(lastTapX, lastTapY); + mLastTapTime = 0; + } + else + { + mLastTapTime = now; + lastTapX = e.getX(); + lastTapY = e.getY(); + doSingleTap(lastTapX, lastTapY); + mTapStatus = 1; + } + + return false; + } + + private void scaleChildren() + { + // scale children + int numPages = getPageCount(); + for (int i = 0; i < numPages; i++) + { + DocPageView cv = (DocPageView) getOrCreateChild(i); + cv.setNewScale(mScale); + } + } + + public boolean onScale(ScaleGestureDetector detector) + { + // new scale factor + float previousScale = mScale; + mScale = Math.min(Math.max(mScale * detector.getScaleFactor(), MIN_SCALE), MAX_SCALE); + + // did we really scale? + if (mScale == previousScale) + return true; + + // scale children + scaleChildren(); + + // maintain focus while scaling + double scale = mScale / previousScale; + double currentFocusX = detector.getFocusX(); + double currentFocusY = detector.getFocusY(); + double viewFocusX = (int) currentFocusX + getScrollX(); + double viewFocusY = (int) currentFocusY + getScrollY(); + int diffX = (int) (viewFocusX * (1 - scale)); + int diffY = (int) (viewFocusY * (1 - scale)); + mXScroll += diffX; + mYScroll += diffY; + + 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; + + return true; + } + + public void onScaleEnd(ScaleGestureDetector detector) + { + // When a pinch-scale is done, we want to get n-across + // to fit properly. + + // get current viewport + Rect viewport = new Rect(); + getGlobalVisibleRect(viewport); + + // if we're at one column and wider than the viewport, + // leave it alone. + if (mLastLayoutColumns == 0 && mPageCollectionWidth >= viewport.width()) + { + mScaling = false; + return; + } + + // ratio of the viewport width to layout width + float ratio = ((float) (viewport.width())) / ((float) (mPageCollectionWidth)); + + // set a new scale factor + mScale *= ratio; + scaleChildren(); + + // scroll so the left edged is flush to the viewport. + mXScroll += getScrollX(); + + requestLayout(); + + mScaling = false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) + { + + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) + { + // do something when user interaction begins + } + + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) + { + // do something when user interaction ends + triggerRender(); + } + + mScaleGestureDetector.onTouchEvent(event); + mGestureDetector.onTouchEvent(event); + + return true; + } + + protected int getPageCount() + { + return getAdapter().getCount(); + } + + protected void onLayout(boolean changed, int left, int top, int right, int bottom) + { + + super.onLayout(changed, left, top, right, bottom); + + if (!mStarted) + return; + + // not if there are no pages + if (getPageCount() == 0) + return; + + int numDocPages = getPageCount(); + + // not if we've been finished + if (finished()) + return; + + // perform any pending scrolling + scrollBy(-mXScroll, -mYScroll); + mXScroll = mYScroll = 0; + + // get current viewport + mViewportOrigin.set(getScrollX(), getScrollY()); + getGlobalVisibleRect(mViewport); + mViewport.offsetTo(mViewportOrigin.x, mViewportOrigin.y); + + // find the widest child + int maxw = 0; + int numPages = getPageCount(); + for (int i = 0; i < numPages; i++) + { + DocPageView cv = (DocPageView) getOrCreateChild(i); + + int childWidth = cv.getCalculatedWidth(); + if (childWidth > maxw) + maxw = childWidth; + } + + // how many columns? + double dcol = (double) (mViewport.width() + GAP) / (double) (maxw + GAP); + int columns = (int) dcol; + + // lay them out + mostVisibleChild = -1; + int mostVisibleChildHeight = -1; + int childTop = 0; + mPageCollectionHeight = 0; + mPageCollectionWidth = 0; + int column = 0; + mBlockRect.setEmpty(); + + for (int i = 0; i < numDocPages; i++) + { + DocPageView cv = (DocPageView) getOrCreateChild(i); + int childWidth = cv.getCalculatedWidth(); + int childHeight = cv.getCalculatedHeight(); + int childLeft = column * (maxw + GAP); + int childRight = childLeft + childWidth; + int childBottom = childTop + childHeight; + mChildRect.set(childLeft, childTop, childRight, childBottom); + + // stash the rect in the page view for later use. + cv.setChildRect(mChildRect); + + // at each layout, we remember the entire width and height of the laid-out + // pages. This is used in applying constraints to scrolling amounts. + if (childBottom > mPageCollectionHeight) + mPageCollectionHeight = childBottom; + if (childRight > mPageCollectionWidth) + mPageCollectionWidth = childRight; + + if (mBlockRect.isEmpty()) + mBlockRect.set(mChildRect); + else + mBlockRect.union(mChildRect); + + if (mChildRect.intersect(mViewport) && i < numDocPages) + { + // visible, so include in layout + if (cv.getParent() == null) + addChildToLayout(cv); + cv.layout(childLeft, childTop, childRight, childBottom); + cv.invalidate(); + + // determine the "most visible" child. + if (cv.getGlobalVisibleRect(mostVisibleRect)) + { + int h = mostVisibleRect.height(); + if (h > mostVisibleChildHeight) + { + mostVisibleChildHeight = h; + mostVisibleChild = i; + } + } + } + else + { + // not visible, so remove from layout + removeViewInLayout(cv); + } + + column++; + if (column >= columns) + { + column = 0; + childTop += childHeight; + childTop += GAP; + } + } + + // if the number of columns has changed, do some scrolling to adjust + if (mScaling && columns >= 1 && mLastLayoutColumns >= 1 && mLastLayoutColumns != columns) + { + // x - center in the viewport + int dx = mBlockRect.centerX() - mViewport.centerX(); + scrollBy(dx, 0); + + // y - attempt to keep what's in the center of the viewport in view. + int oldy = mViewport.centerY() - mLastBlockRect.top; + int newy = (int) ((float) oldy * mBlockRect.height() / mLastBlockRect.height()); + scrollBy(0, newy - oldy); + } + mLastLayoutColumns = columns; + mLastBlockRect.set(mBlockRect); + + // see if we're handling a start page + handleStartPage(); + + triggerRender(); + } + + public int getMostVisiblePage() + { + return mostVisibleChild; + } + + // start page, get and set. + private int mStartPage = 0; + + public void setStartPage(int page) + { + mStartPage = page; + } + + protected int getStartPage() + { + return mStartPage; + } + + // handle start page + public void handleStartPage() + { + // if we've been given a start page, go there. + final int start = getStartPage(); + if (start > 0) + { + setStartPage(0); // but just once + + // post all of this so that we get an additional layout request + final Handler handler = new Handler(); + handler.post(new Runnable() + { + @Override + public void run() + { + DocPageView cv = (DocPageView) getOrCreateChild(start - 1); + Rect r = cv.getChildRect(); + scrollBy(0, r.top); + requestLayout(); + } + }); + } + } + + // override the view's scrollBy() function so we can + // take the opportunity to apply some constraints + + @Override + public void scrollBy(int dx, int dy) + { + Point p = constrainScrollBy(dx, dy); + super.scrollBy(p.x, p.y); + } + + // apply contraints to every scroll request. + + protected Point constrainScrollBy(int dx, int dy) + { + int vph; + int vpw; + { + Rect viewport = new Rect(); + getGlobalVisibleRect(viewport); + vph = viewport.height(); + vpw = viewport.width(); + } + int sx = getScrollX(); + int sy = getScrollY(); + + if (mPageCollectionWidth <= vpw) + { + // not too far to the right + if (mPageCollectionWidth - sx - dx > vpw) + dx = 0; + + // not too far to the left + if (sx + dx > 0) + dx = -sx; + } + else + { + // not too far to the right + if (mPageCollectionWidth < sx + vpw + dx) + dx = 0; + + // not too far to the left + if (sx + dx < 0) + dx = -sx; + } + + if (mPageCollectionHeight <= vph) + { + // not too far down + if (mPageCollectionHeight - sy - dy > vph) + dy = 0; + + // not too far up + if (sy + dy > 0) + dy = -sy; + } + else + { + // not too far down + if (sy + dy < 0) + dy = -sy; + + // not too far up. + if (mPageCollectionHeight + 2 * vph / 3 < sy + vph + dy) + dy = 0; + } + + return new Point(dx, dy); + } + + @Override + public Adapter getAdapter() + { + return mAdapter; + } + + @Override + public View getSelectedView() + { + return null; + } + + @Override + public void setAdapter(Adapter adapter) + { + mAdapter = (PageAdapter) adapter; + requestLayout(); + } + + @Override + public void setSelection(int arg0) + { + throw new UnsupportedOperationException("setSelection is not supported"); + } + + private View getCached() + { + return null; + } + + protected View getOrCreateChild(int i) + { + View v = mChildViews.get(i); + if (v == null) + { + v = getViewFromAdapter(i); + mChildViews.append(i, v); // Record the view against it's adapter index + onScaleChild(v, mScale); + } + + return v; + } + + protected View getViewFromAdapter(int index) + { + return getAdapter().getView(index, getCached(), this); + } + + private void addChildToLayout(View v) + { + LayoutParams params = v.getLayoutParams(); + if (params == null) + { + params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + addViewInLayout(v, 0, params, true); + } + + protected void triggerRender() + { + // Note that a render is needed + renderRequested = true; + + // If the previous render has completed, start a new one. Otherwise + // a new one will start as soon as the previous one completes. + if (renderCount == 0) + renderPages(); + } + + private void renderPages() + { + renderRequested = false; + + if (mFinished) + return; + + if (bitmaps == null) + return; + + // Rotate to the next bitmap + bitmapIndex++; + if (bitmapIndex >= bitmaps.length) + bitmapIndex = 0; + + // iterate through the children + int numPages = getPageCount(); + for (int i = 0; i < numPages; i++) + { + if (mFinished) + return; + + final DocPageView cv = (DocPageView) getOrCreateChild(i); + if (cv.getParent() != null && cv.isReallyVisible()) + { + // Count up as we kick off rendering of each visible page + renderCount++; + cv.render(bitmaps[bitmapIndex], new RenderListener() + { + @Override + public void progress(int error) + { + + if (error == 0) + cv.invalidate(); + + // Count down as they complete + renderCount--; + + if (renderCount == 0) + { + if (renderRequested) + { + // If this phase of rendering has completed and another has + // been requested, start it now + renderPages(); + } + } + } + }, mShowAnnotations); + } + } + } + + @Override + 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; + + // limit the amount of repeated layouts. + long tNow = System.currentTimeMillis(); + long diff = tNow - mFlingStartTime; + if (diff > FLING_THROTTLE_TIME) + { + requestLayout(); + mFlingStartTime = tNow; + } + + mStepper.prod(); + } + else + { + // one more + long tNow = System.currentTimeMillis(); + if (tNow != mFlingStartTime) + requestLayout(); + } + } + + public void finish() + { + // we're done with this view. + mFinished = true; + + // first, hide and remove all the children + int numPages = getPageCount(); + for (int i = 0; i < numPages; i++) + { + DocPageView cv = (DocPageView) getOrCreateChild(i); + cv.setVisibility(GONE); + removeViewInLayout(cv); + cv.finish(); + } + } + + public boolean finished() + { + return mFinished; + } + + protected void smoothScrollBy(int dx, int dy) + { + mScrollerLastX = mScrollerLastY = 0; + mScroller.startScroll(0, 0, dx, dy, 400); + mStepper.prod(); + } + + public void scrollToPage(int pageNumber) + { + // scroll to bring the page into view + + // get current viewport + Rect viewport = new Rect(); + getGlobalVisibleRect(viewport); + + // offset it based on current scroll position + Point viewportOrigin = new Point(); + viewportOrigin.set(getScrollX(), getScrollY()); + viewport.offsetTo(viewportOrigin.x, viewportOrigin.y); + + // get page rect from last layout + DocPageView cv = (DocPageView) getOrCreateChild(pageNumber); + Rect childRect = cv.getChildRect(); + + // scroll + if ((childRect.height()) > viewport.height()) + { + // put the top of the page at the top and the left at 0 + smoothScrollBy(getScrollX(), getScrollY() - childRect.top); + } + else + { + // if the whole page is not visible, move the center of the page at the center + if (childRect.top < viewport.top || childRect.bottom > viewport.bottom) + { + if (childRect.top == 0) + smoothScrollBy(0, getScrollY()); + else + smoothScrollBy(0, getScrollY() + viewport.height() / 2 - (childRect.bottom + childRect.top) / 2); + } + } + } + + private Point viewToScreen(Point p) + { + Point newPoint = new Point(p); + + Rect r = new Rect(); + this.getGlobalVisibleRect(r); + + newPoint.offset(r.left, r.top); + + return newPoint; + } + + public void scrollBoxIntoView(int pageNum, RectF box) + { + // get our viewport + Rect viewport = new Rect(); + getGlobalVisibleRect(viewport); + viewport.offset(0, -viewport.top); + + // get the location of the box's lower left corner, + // relative to the viewport + DocPageView cv = (DocPageView) getOrCreateChild(pageNum); + Point point = cv.pageToView((int) box.left, (int) box.bottom); + Rect childRect = cv.getChildRect(); + point.y += childRect.top; + point.y -= getScrollY(); + + // if the point is outside the viewport, scroll so it is. + if (point.y < viewport.top || point.y >= viewport.bottom) + { + int diff = (viewport.top + viewport.bottom) / 2 - point.y; + smoothScrollBy(0, diff); + } + } + + public void toggleAnnotations() + { + mShowAnnotations = !mShowAnnotations; + triggerRender(); + } + + public void onShowPages() + { + int page_width_percentage = getContext().getResources().getInteger(R.integer.page_width_percentage); + int pagelist_width_percentage = getContext().getResources().getInteger(R.integer.pagelist_width_percentage); + + onSizeChange((float) (page_width_percentage) / (float) (page_width_percentage + pagelist_width_percentage)); + } + + public void onHidePages() + { + int page_width_percentage = getContext().getResources().getInteger(R.integer.page_width_percentage); + int pagelist_width_percentage = getContext().getResources().getInteger(R.integer.pagelist_width_percentage); + + onSizeChange((float) (page_width_percentage + pagelist_width_percentage) / (float) (page_width_percentage)); + } + +} diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DragHandle.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DragHandle.java new file mode 100644 index 00000000..c2bceb47 --- /dev/null +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DragHandle.java @@ -0,0 +1,179 @@ +package com.artifex.mupdf.android; + +import android.content.Context; +import android.graphics.Point; +import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; + +public class DragHandle extends FrameLayout implements View.OnTouchListener +{ + // for tracking movement + private int mDragDeltaX; + private int mDragDeltaY; + private boolean mIsDragging = false; + + // the actual current position + private int mPositionX = 0; + private int mPositionY = 0; + + // DragHandleListener + private DragHandleListener mDragHandleListener; + + // kinds for selection + public static final int SELECTION_TOP_LEFT = 1; + public static final int SELECTION_BOTTOM_RIGHT = 2; + + // kinds for resizing + public static final int RESIZE_TOP_LEFT = 3; + public static final int RESIZE_TOP_RIGHT = 4; + public static final int RESIZE_BOTTOM_LEFT = 5; + public static final int RESIZE_BOTTOM_RIGHT = 6; + + // kind for dragging and rotating + public static final int DRAG = 7; + public static final int ROTATE = 8; + + private int mKind = 0; + + public DragHandle(Context context, int resource, int kind) + { + super(context); + + mDragHandleListener = null; + mKind = kind; + + // inflate with the given resource + View.inflate(context, resource, this); + + // set our touch listener + setOnTouchListener(this); + } + + public int getKind() + { + return mKind; + } + + // test to see if this handle is a selection handle + public boolean isSelectionKind() + { + return (mKind == SELECTION_TOP_LEFT || mKind == SELECTION_BOTTOM_RIGHT); + } + + // test to see if this handle is a resize handle + public boolean isResizeKind() + { + return (mKind == RESIZE_TOP_LEFT || + mKind == RESIZE_TOP_RIGHT || + mKind == RESIZE_BOTTOM_LEFT || + mKind == RESIZE_BOTTOM_RIGHT); + } + + // test to see if this handle is a drag handle + public boolean isDragKind() + { + return (mKind == DRAG); + } + + // test to see if this handle is a rotate handle + public boolean isRotateKind() + { + return (mKind == ROTATE); + } + + public void setDragHandleListener(DragHandleListener listener) + { + mDragHandleListener = listener; + } + + // this view is shown at the corners of a selection. + // We use a touch listener to drag it within its parent. + // It's parent is a RelativeLayout, so we effect moving by adjusting + // offsets. The actual top and left are always 0,0. + + @Override + public boolean onTouch(View view, MotionEvent event) + { + final int X = (int) event.getRawX(); + final int Y = (int) event.getRawY(); + + final DragHandle theHandle = this; + + switch (event.getAction() & MotionEvent.ACTION_MASK) + { + case MotionEvent.ACTION_DOWN: + Point position = getPosition(); + mDragDeltaX = X - position.x; + mDragDeltaY = Y - position.y; + mIsDragging = true; + + if (mDragHandleListener != null) + { + mDragHandleListener.onStartDrag(theHandle); + } + + break; + + case MotionEvent.ACTION_UP: + mIsDragging = false; + + if (mDragHandleListener != null) + { + mDragHandleListener.onEndDrag(theHandle); + } + + break; + + case MotionEvent.ACTION_MOVE: + + moveTo(X - mDragDeltaX, Y - mDragDeltaY); + + if (mDragHandleListener != null) + { + mDragHandleListener.onDrag(theHandle); + } + + break; + } + return true; + } + + public void show(boolean bShow) + { + if (bShow) + setVisibility(View.VISIBLE); + else + setVisibility(View.GONE); + } + + public Point getPosition() + { + return new Point(mPositionX, mPositionY); + } + + public void moveTo(int x, int y) + { + offsetLeftAndRight(x - mPositionX); + offsetTopAndBottom(y - mPositionY); + + mPositionX = x; + mPositionY = y; + + invalidate(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) + { + super.onLayout(changed, left, top, right, bottom); + + // we control the position by setting offsets + // The actual position is always 0,0. + // Because of this, we need to reapply the offsets here. + + offsetLeftAndRight(mPositionX); + offsetTopAndBottom(mPositionY); + } + +} diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DragHandleListener.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DragHandleListener.java new file mode 100644 index 00000000..60c4e3ea --- /dev/null +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DragHandleListener.java @@ -0,0 +1,10 @@ +package com.artifex.mupdf.android; + +public interface DragHandleListener +{ + void onStartDrag(DragHandle handle); + + void onDrag(DragHandle handle); + + void onEndDrag(DragHandle handle); +} diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/PageAdapter.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/PageAdapter.java index 0a1d8c05..6de56b6a 100644 --- a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/PageAdapter.java +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/PageAdapter.java @@ -13,26 +13,36 @@ public class PageAdapter extends BaseAdapter private final Context mContext; private Document mDoc; - public PageAdapter(Context c) { + public PageAdapter(Context c) + { mContext = c; } - public void setDocument(Document doc) { + public void setDocument(Document doc) + { mDoc = doc; } + private int mWidth; - public void setWidth(int w) {mWidth=w;} + + public void setWidth(int w) + { + mWidth = w; + } @Override - public int getCount() { + public int getCount() + { return mDoc.countPages(); } - public Object getItem(int position) { + public Object getItem(int position) + { return null; // not used } - public long getItemId(int position) { + public long getItemId(int position) + { return 0; // not used } @@ -41,7 +51,7 @@ public class PageAdapter extends BaseAdapter // make or reuse a view DocPageView pageView; - final Activity activity = (Activity)mContext; + final Activity activity = (Activity) mContext; if (convertView == null) { // make a new one diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/Stepper.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/Stepper.java index 9275cf99..aeef6704 100644 --- a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/Stepper.java +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/Stepper.java @@ -4,33 +4,44 @@ import android.annotation.SuppressLint; import android.os.Build; import android.view.View; -public class Stepper { +public class Stepper +{ private final View mPoster; private final Runnable mTask; private boolean mPending; - public Stepper(View v, Runnable r) { + public Stepper(View v, Runnable r) + { mPoster = v; mTask = r; mPending = false; } @SuppressLint("NewApi") - public void prod() { - if (!mPending) { + public void prod() + { + if (!mPending) + { mPending = true; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - mPoster.postOnAnimation(new Runnable() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + { + mPoster.postOnAnimation(new Runnable() + { @Override - public void run() { + public void run() + { mPending = false; mTask.run(); } }); - } else { - mPoster.post(new Runnable() { + } + else + { + mPoster.post(new Runnable() + { @Override - public void run() { + public void run() + { mPending = false; mTask.run(); } diff --git a/platform/android/example/mupdf/src/main/res/drawable/button.xml b/platform/android/example/mupdf/src/main/res/drawable/button.xml new file mode 100644 index 00000000..9258d530 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/button.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true"> + <shape> + <solid android:color="@color/button_pressed" /> + <corners android:radius="12dp"/> + <padding android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp" /> + </shape> + </item> + <item android:state_focused="true"> + <shape> + <solid android:color="@color/button_normal" /> + <padding android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp" /> + </shape> + </item> + <item android:state_selected="true"> + <shape> + <solid android:color="@color/button_selected" /> + <corners android:radius="12dp"/> + <padding android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp" /> + </shape> + </item> + <item> + <shape> + <solid android:color="@color/button_normal" /> + <padding android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp" /> + </shape> + </item> +</selector> diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_back.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_back.xml new file mode 100644 index 00000000..8973b8cd --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_back.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> + + <path + android:strokeColor="#333333" + android:strokeWidth="2.518" + android:pathData="M 8.353 24.329 L 43.359 24.329" /> + <path + android:strokeColor="#333333" + android:strokeWidth="2.518" + android:pathData="M 20.729 11.168 L 7.789 24.297 L 20.825 36.58 " /> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_find.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_find.xml new file mode 100644 index 00000000..71020d8f --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_find.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> + + <path + android:fillColor="#333333" + android:pathData="M22.205,8.403c6.879,0,12.452,5.574,12.452,12.452 +c0,6.876-5.573,12.45-12.452,12.45c-6.877,0-12.451-5.574-12.451-12.45C9.754,13.977,15.328,8.403,22.205,8.403 +M22.205,10.016 +c-5.977,0-10.838,4.862-10.838,10.839c0,5.976,4.861,10.838,10.838,10.838c5.977,0,10.84-4.862,10.84-10.838 +C33.045,14.877,28.182,10.016,22.205,10.016" /> + <path + android:strokeColor="#333333" + android:strokeWidth="2.348" + android:pathData="M 41.27 40.573 L 29.534 28.836" /> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_find_next.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_find_next.xml new file mode 100755 index 00000000..57d0f47e --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_find_next.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> + + <path + android:fillColor="#333333" + android:pathData="M17.508,11.042c5.84,0,10.572,4.733,10.572,10.573 +c0,5.84-4.732,10.573-10.572,10.573S6.935,27.455,6.935,21.615C6.935,15.775,11.668,11.042,17.508,11.042 +M17.508,13.391 +c-4.535,0-8.225,3.689-8.225,8.224s3.69,8.225,8.225,8.225s8.224-3.689,8.224-8.225S22.043,13.391,17.508,13.391" /> + <path + android:strokeColor="#333333" + android:strokeWidth="2.348" + android:pathData="M 33.695 38.359 L 23.73 28.395" /> + <path + android:fillColor="#333333" + android:pathData="M 31.18 19.878 H 37.413 V 24.173 H 31.18 V 19.878 Z" /> + <path + android:fillColor="#333333" + android:pathData="M 36.143 27.409 L 41.59 21.981 L 36.143 16.533 " /> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_find_previous.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_find_previous.xml new file mode 100755 index 00000000..83f0295c --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_find_previous.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> + + <path + android:fillColor="#333333" + android:pathData="M17.508,11.042c5.84,0,10.572,4.733,10.572,10.573 +c0,5.84-4.732,10.573-10.572,10.573S6.935,27.455,6.935,21.615C6.935,15.775,11.668,11.042,17.508,11.042 +M17.508,13.391 +c-4.535,0-8.225,3.689-8.225,8.224s3.69,8.225,8.225,8.225s8.224-3.689,8.224-8.225S22.043,13.391,17.508,13.391" /> + <path + android:strokeColor="#333333" + android:strokeWidth="2.348" + android:pathData="M 33.695 38.359 L 23.73 28.395" /> + <path + android:fillColor="#333333" + android:pathData="M 34.836 19.878 H 41.068 V 24.173 H 34.836 V 19.878 Z" /> + <path + android:fillColor="#333333" + android:pathData="M 36.107 27.409 L 30.66 21.981 L 36.107 16.533 " /> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_redo.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_redo.xml new file mode 100755 index 00000000..0d4c4be8 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_redo.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> + + <path + android:strokeColor="#333333" + android:strokeWidth="1.56" + android:pathData="M 28.605 14.55 L 36.42 22.479 L 28.549 29.896 " /> + <path + android:strokeColor="#333333" + android:strokeWidth="1.56" + android:pathData="M35.627,22.195 +c0,0-8.268-0.113-12.004,0.453c-3.737,0.567-9.741,2.04-8.948,11.778" /> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_save.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_save.xml new file mode 100644 index 00000000..0aa6421c --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_save.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> + + <path + android:fillColor="#333333" + android:pathData="M44.035,43.959H5.965V6.042h38.07V43.959z +M42.663,7.414H7.338v35.173h35.325V7.414z" /> + <path + android:fillColor="#333333" + android:pathData="M38.359,6.041v12.446c0,1.007-0.812,1.82-1.82,1.82h-23.23 +c-1.005,0-1.822-0.813-1.822-1.82V6.041H38.359 +M36.987,7.414H12.86v11.073c0,0.247,0.201,0.448,0.449,0.448h23.23 +c0.251,0,0.448-0.197,0.448-0.448V7.414z" /> + <path + android:strokeColor="#333333" + android:strokeWidth="1.372" + android:strokeMiterLimit="10" + android:pathData="M 33.484 9.823 L 33.484 16.167" /> + <path + android:strokeColor="#333333" + android:strokeWidth="1.372" + android:strokeMiterLimit="10" + android:pathData="M 11.487 30.369 L 38.605 30.369" /> + <path + android:strokeColor="#333333" + android:strokeWidth="1.372" + android:strokeMiterLimit="10" + android:pathData="M 11.487 35.959 L 38.605 35.959" /> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_selection_drag_handle.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_selection_drag_handle.xml new file mode 100644 index 00000000..c5ae201f --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_selection_drag_handle.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="20dp" + android:height="20dp" + android:viewportWidth="20" + android:viewportHeight="20"> + + <path + android:fillColor="#4683C4" + android:pathData="M0,10 C0,4.478,4.478,0,10,0 C15.522,0,20,4.478,20,10 C20,15.523,15.522,20,10,20 +C4.478,20,0,15.523,0,10" /> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_selection_hand.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_selection_hand.xml new file mode 100644 index 00000000..edefdd5b --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_selection_hand.xml @@ -0,0 +1,96 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="30dp" + android:height="30dp" + android:viewportWidth="30" + android:viewportHeight="30"> + + <clip-path + android:pathData="M0,0 L100,0 L100,100 L0,100 L0,0 Z" /> + <path + android:fillColor="#549fd7" + android:pathData="M15,0.25 C23.147,0.25,29.75,6.854,29.75,15 C29.75,23.147,23.147,29.75,15,29.75 +C6.854,29.75,0.25,23.147,0.25,15 C0.25,6.854,6.854,0.25,15,0.25" /> + <clip-path + android:pathData="M0,0 L100,0 L100,100 L0,100 L0,0 Z" /> + <path + android:strokeColor="#176c9a" + android:strokeWidth="0.52700001" + android:pathData="M15,0.25 C23.147,0.25,29.75,6.854,29.75,15 C29.75,23.147,23.147,29.75,15,29.75 +C6.854,29.75,0.25,23.147,0.25,15 C0.25,6.854,6.854,0.25,15,0.25" /> + <clip-path + android:pathData="M13.531,6.182 L13.531,7.291 C13.531,7.291,12.91,6.582,12.11,6.582 +C11.311,6.582,10.69,7.514,10.69,7.514 L10.645,14.394 +C10.645,14.394,8.484,12.199,7.255,12.199 C7.053,12.199,6.877,12.257,6.739,12.397 +C5.764,13.374,7.649,16.281,7.848,16.48 C8.048,16.68,10.401,20.586,10.6,20.787 +C10.8,20.985,12.82,24.381,16.239,24.381 +C19.658,24.381,21.878,20.743,21.878,19.988 L21.878,10.178 +C21.878,10.178,21.922,8.682,20.452,8.682 C19.387,8.682,19.124,9.599,19.124,9.599 +L19.124,7.423 C19.124,7.423,18.592,6.582,17.881,6.582 +C17.172,6.582,16.461,6.937,16.461,7.336 L16.461,13.951 L16.416,6.182 +C16.416,6.182,16.106,5.116,14.908,5.116 C13.709,5.116,13.531,6.182,13.531,6.182" /> + <path + android:fillColor="#dae8f4" + android:strokeColor="#dae8f4" + android:strokeWidth="1" + android:pathData="M5.50976,4.98888 L21.6678,4.98888 L21.6678,24.2539 L5.50976,24.2539 +L5.50976,4.98888 Z" /> + <group> + <clip-path + android:pathData="M0,0 L30,0 L30,30 L0,30 L0,0 Z" /> + <clip-path + android:pathData="M0,0 L100,0 L100,100 L0,100 L0,0 Z" /> + <path + android:strokeColor="#166c9c" + android:strokeWidth="0.52700001" + android:pathData="M14.908,5.116 C13.709,5.116,13.532,6.182,13.532,6.182 L13.532,7.291 +C13.532,7.291,12.91,6.582,12.111,6.582 C11.312,6.582,10.69,7.513,10.69,7.513 +L10.646,14.393 C10.646,14.393,8.484,12.199,7.255,12.199 +C7.054,12.199,6.877,12.258,6.74,12.396 C5.763,13.374,7.649,16.281,7.848,16.48 +C8.048,16.68,10.401,20.586,10.601,20.787 +C10.801,20.984,12.821,24.381,16.239,24.381 +C19.658,24.381,21.879,20.742,21.879,19.988 L21.879,10.178 +C21.879,10.178,21.923,8.681,20.453,8.681 C19.388,8.681,19.125,9.599,19.125,9.599 +L19.125,7.424 C19.125,7.424,18.593,6.582,17.882,6.582 +C17.173,6.582,16.462,6.937,16.462,7.336 L16.462,13.951 L16.417,6.182 +C16.416,6.182,16.106,5.116,14.908,5.116" /> + </group> + <group> + <clip-path + android:pathData="M0,0 L30,0 L30,30 L0,30 L0,0 Z" /> + <clip-path + android:pathData="M13.537,13.92 L13.537,7.467 L13.537,13.92 Z" /> + <path + android:fillColor="#549fd7ff" + android:pathData="M13.537,13.92 L13.537,7.467 L13.537,13.92 Z" /> + </group> + <group> + <clip-path + android:pathData="M0,0 L30,0 L30,30 L0,30 L0,0 Z" /> + <clip-path + android:pathData="M0,0 L100,0 L100,100 L0,100 L0,0 Z" /> + <path + android:strokeColor="#166c9c" + android:strokeWidth="0.52700001" + android:pathData="M13.537,13.919 L13.537,7.466" /> + </group> + <group> + <clip-path + android:pathData="M0,0 L30,0 L30,30 L0,30 L0,0 Z" /> + <clip-path + android:pathData="M19.094,14.025 L19.094,7.572 L19.094,14.025 Z" /> + <path + android:fillColor="#549fd7ff" + android:pathData="M19.094,14.025 L19.094,7.572 L19.094,14.025 Z" /> + </group> + <group> + <clip-path + android:pathData="M0,0 L30,0 L30,30 L0,30 L0,0 Z" /> + <clip-path + android:pathData="M0,0 L100,0 L100,100 L0,100 L0,0 Z" /> + <path + android:strokeColor="#166c9c" + android:strokeWidth="0.52700001" + android:pathData="M19.094,14.025 L19.094,7.572" /> + </group> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_selection_rotate.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_selection_rotate.xml new file mode 100644 index 00000000..91d00104 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_selection_rotate.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="20dp" + android:height="20dp" + android:viewportWidth="20" + android:viewportHeight="20"> + + <clip-path + android:pathData="M0,0 L20,0 L20,20 L0,20 L0,0 Z" /> + <path + android:fillColor="#FFFFFF" + android:pathData="M0,10 C0,4.477,4.477,0,9.999,0 C15.522,0,20,4.477,20,10 +C20,15.523,15.522,20,9.999,20 C4.477,20,0,15.523,0,10" /> + <clip-path + android:pathData="M0,0 L20,0 L20,20 L0,20 L0,0 Z" /> + <path + android:strokeColor="#4683C4" + android:strokeWidth="1.04" + android:pathData="M10.806,5.468 C13.296,5.855,15.202,8.008,15.202,10.607 +C15.202,13.479,12.873,15.807,9.999,15.807 C7.128,15.807,4.8,13.479,4.8,10.607 +C4.8,10.394,4.812,10.184,4.837,9.979" /> + <clip-path + android:pathData="M0,0 L20,0 L20,20 L0,20 L0,0 Z" /> + <path + android:fillColor="#4683C4" + android:pathData="M11.462,7.991 L7.925,5.506 L11.462,3.009 Z" /> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_undo.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_undo.xml new file mode 100755 index 00000000..b4ce9622 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/icon_undo.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> + + <path + android:strokeColor="#333333" + android:strokeWidth="1.56" + android:pathData="M 22.418 14.55 L 14.603 22.478 L 22.475 29.896 " /> + <path + android:strokeColor="#333333" + android:strokeWidth="1.56" + android:pathData="M15.396,22.196 +c0,0,8.268-0.113,12.005,0.452c3.737,0.567,9.741,2.039,8.948,11.779" /> +</vector>
\ No newline at end of file diff --git a/platform/android/example/mupdf/src/main/res/drawable/search_button.xml b/platform/android/example/mupdf/src/main/res/drawable/search_button.xml new file mode 100644 index 00000000..c741b62c --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/search_button.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + + <item android:state_selected="true"> + <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- This is the line --> + <item> + <shape> + <padding android:left="1dp" android:top="2dp" android:right="1dp" android:bottom="0dp"/> + <solid android:color="#FFFFFFFF" /> + <corners + android:bottomRightRadius="0.1dp" + android:bottomLeftRadius="0.1dp" + android:topLeftRadius="12dp" + android:topRightRadius="12dp" /> + </shape> + </item> + <!-- This is the main color --> + <item> + <shape> + <solid android:color="@color/toolbar" /> + <corners + android:bottomRightRadius="0.1dp" + android:bottomLeftRadius="0.1dp" + android:topLeftRadius="12dp" + android:topRightRadius="12dp" /> + </shape> + </item> + </layer-list> + </item> + +</selector> diff --git a/platform/android/example/mupdf/src/main/res/drawable/search_input_wrapper.xml b/platform/android/example/mupdf/src/main/res/drawable/search_input_wrapper.xml new file mode 100644 index 00000000..924e3fe8 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/search_input_wrapper.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/white" /> + <stroke android:width="1dp" android:color="#888888"/> + <corners + android:bottomRightRadius="24dp" + android:bottomLeftRadius="24dp" + android:topLeftRadius="24dp" + android:topRightRadius="24dp" /> +</shape> diff --git a/platform/android/example/mupdf/src/main/res/drawable/search_text_input.xml b/platform/android/example/mupdf/src/main/res/drawable/search_text_input.xml new file mode 100644 index 00000000..9be97bd4 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/search_text_input.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + + <stroke android:width="1dp" + android:color="@color/transparent" /> + + <solid android:color="@color/transparent" /> + + <padding + android:top="0dp" + android:left="10dp" + android:bottom="0dp" + /> + +</shape> diff --git a/platform/android/example/mupdf/src/main/res/drawable/tab_left_selected.xml b/platform/android/example/mupdf/src/main/res/drawable/tab_left_selected.xml new file mode 100644 index 00000000..c466e842 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/tab_left_selected.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- This is the line --> + <item> + <shape> + <padding android:left="1dp" android:top="2dp" android:right="1dp" android:bottom="0dp"/> + <solid android:color="#FFFFFFFF" /> + <corners + android:bottomRightRadius="0.1dp" + android:bottomLeftRadius="0.1dp" + android:topLeftRadius="12dp" + android:topRightRadius="0.1dp" /> + </shape> + </item> + <!-- This is the main color --> + <item> + <shape> + <solid android:color="@color/toolbar" /> + <corners + android:bottomRightRadius="0.1dp" + android:bottomLeftRadius="0.1dp" + android:topLeftRadius="12dp" + android:topRightRadius="0.1dp" /> + </shape> + </item> +</layer-list> diff --git a/platform/android/example/mupdf/src/main/res/drawable/tab_left_selector.xml b/platform/android/example/mupdf/src/main/res/drawable/tab_left_selector.xml new file mode 100644 index 00000000..fe947ca9 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/tab_left_selector.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + + <!-- Active tab --> + <item android:state_selected="true" android:state_focused="false" + android:state_pressed="false" android:drawable="@drawable/tab_left_selected" /> + + <!-- Inactive tab --> + <item android:state_selected="false" android:state_focused="false" + android:state_pressed="false" android:drawable="@drawable/tab_left_unselected" /> + + <!-- Pressed tab --> + <item android:state_pressed="true" android:drawable="@color/transparent" /> + + <!-- Selected tab (using d-pad) --> + <item android:state_focused="true" android:state_selected="true" + android:state_pressed="false" android:drawable="@color/transparent" /> + +</selector> diff --git a/platform/android/example/mupdf/src/main/res/drawable/tab_left_unselected.xml b/platform/android/example/mupdf/src/main/res/drawable/tab_left_unselected.xml new file mode 100644 index 00000000..e8414614 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/tab_left_unselected.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- This is the line --> + <item> + <shape> + <padding android:left="1dp" android:top="2dp" android:right="1dp" android:bottom="0dp"/> + <solid android:color="#FFFFFFFF" /> + <corners + android:bottomRightRadius="0.1dp" + android:bottomLeftRadius="0.1dp" + android:topLeftRadius="12dp" + android:topRightRadius="0.1dp" /> + </shape> + </item> + <!-- This is the main color --> + <item> + <shape> + <solid android:color="#1B63BB" /> + <corners + android:bottomRightRadius="0.1dp" + android:bottomLeftRadius="0.1dp" + android:topLeftRadius="12dp" + android:topRightRadius="0.1dp" /> + </shape> + </item> +</layer-list> diff --git a/platform/android/example/mupdf/src/main/res/drawable/tab_right_selected.xml b/platform/android/example/mupdf/src/main/res/drawable/tab_right_selected.xml new file mode 100644 index 00000000..99121fd8 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/tab_right_selected.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- This is the line --> + <item> + <shape> + <padding android:left="1dp" android:top="2dp" android:right="1dp" android:bottom="0dp"/> + <solid android:color="#FFFFFFFF" /> + <corners + android:bottomRightRadius="0.1dp" + android:bottomLeftRadius="0.1dp" + android:topLeftRadius="0.1dp" + android:topRightRadius="12dp" /> + </shape> + </item> + <!-- This is the main color --> + <item> + <shape> + <solid android:color="@color/toolbar" /> + <corners + android:bottomRightRadius="0.1dp" + android:bottomLeftRadius="0.1dp" + android:topLeftRadius="0.1dp" + android:topRightRadius="12dp" /> + </shape> + </item> +</layer-list> diff --git a/platform/android/example/mupdf/src/main/res/drawable/tab_right_selector.xml b/platform/android/example/mupdf/src/main/res/drawable/tab_right_selector.xml new file mode 100644 index 00000000..8f4d7caa --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/tab_right_selector.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + + <!-- Active tab --> + <item android:state_selected="true" android:state_focused="false" + android:state_pressed="false" android:drawable="@drawable/tab_right_selected" /> + + <!-- Inactive tab --> + <item android:state_selected="false" android:state_focused="false" + android:state_pressed="false" android:drawable="@drawable/tab_right_unselected" /> + + <!-- Pressed tab --> + <item android:state_pressed="true" android:drawable="@color/transparent" /> + + <!-- Selected tab (using d-pad) --> + <item android:state_focused="true" android:state_selected="true" + android:state_pressed="false" android:drawable="@color/transparent" /> + +</selector> diff --git a/platform/android/example/mupdf/src/main/res/drawable/tab_right_unselected.xml b/platform/android/example/mupdf/src/main/res/drawable/tab_right_unselected.xml new file mode 100644 index 00000000..a33e0bcf --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/tab_right_unselected.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- This is the line --> + <item> + <shape> + <padding android:left="1dp" android:top="2dp" android:right="1dp" android:bottom="0dp"/> + <solid android:color="#FFFFFFFF" /> + <corners + android:bottomRightRadius="0.1dp" + android:bottomLeftRadius="0.1dp" + android:topLeftRadius="0.1dp" + android:topRightRadius="12dp" /> + </shape> + </item> + <!-- This is the main color --> + <item> + <shape> + <solid android:color="#1B63BB" /> + <corners + android:bottomRightRadius="0.1dp" + android:bottomLeftRadius="0.1dp" + android:topLeftRadius="0.1dp" + android:topRightRadius="12dp" /> + </shape> + </item> +</layer-list> diff --git a/platform/android/example/mupdf/src/main/res/drawable/tab_selected.xml b/platform/android/example/mupdf/src/main/res/drawable/tab_selected.xml new file mode 100755 index 00000000..335752ab --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/tab_selected.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> +<!-- This is the line --> +<item> + <shape> + <padding android:left="1dp" android:top="2dp" android:right="1dp" android:bottom="0dp"/> + <solid android:color="#FFFFFFFF" /> + </shape> +</item> +<!-- This is the main color --> +<item> + <shape> + <solid android:color="@color/toolbar" /> + </shape> +</item> +</layer-list> diff --git a/platform/android/example/mupdf/src/main/res/drawable/tab_selector.xml b/platform/android/example/mupdf/src/main/res/drawable/tab_selector.xml new file mode 100755 index 00000000..dcb67b72 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/tab_selector.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + + <!-- Active tab --> + <item android:state_selected="true" android:state_focused="false" + android:state_pressed="false" android:drawable="@drawable/tab_selected" /> + + <!-- Inactive tab --> + <item android:state_selected="false" android:state_focused="false" + android:state_pressed="false" android:drawable="@drawable/tab_unselected" /> + + <!-- Pressed tab --> + <item android:state_pressed="true" android:drawable="@color/transparent" /> + + <!-- Selected tab (using d-pad) --> + <item android:state_focused="true" android:state_selected="true" + android:state_pressed="false" android:drawable="@color/transparent" /> + +</selector> diff --git a/platform/android/example/mupdf/src/main/res/drawable/tab_text_selector.xml b/platform/android/example/mupdf/src/main/res/drawable/tab_text_selector.xml new file mode 100755 index 00000000..d25dcd4e --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/tab_text_selector.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true" android:color="@android:color/black" /> + <item android:state_focused="true" android:color="@android:color/white" /> + <item android:state_pressed="true" android:color="@android:color/white" /> + <item android:color="@android:color/white" /> +</selector> diff --git a/platform/android/example/mupdf/src/main/res/drawable/tab_unselected.xml b/platform/android/example/mupdf/src/main/res/drawable/tab_unselected.xml new file mode 100755 index 00000000..a8c76430 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/tab_unselected.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> +<!-- This is the line --> +<item> + <shape> + <padding android:left="1dp" android:top="2dp" android:right="1dp" android:bottom="0dp"/> + <solid android:color="#FFFFFFFF" /> + </shape> +</item> +<!-- This is the main color --> +<item> + <shape> + <solid android:color="#1B63BB" /> + </shape> +</item> +</layer-list> diff --git a/platform/android/example/mupdf/src/main/res/drawable/toolbar_button.xml b/platform/android/example/mupdf/src/main/res/drawable/toolbar_button.xml new file mode 100644 index 00000000..f25b3506 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/drawable/toolbar_button.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true"> + <shape> + <solid android:color="@color/button_pressed" /> + <corners android:radius="12dp"/> + <!--<padding android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp" />--> + </shape> + </item> + <item android:state_focused="true"> + <shape> + <solid android:color="@color/button_normal" /> + <!--<padding android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp" />--> + </shape> + </item> + <item android:state_selected="true"> + <shape> + <solid android:color="@color/button_selected" /> + <corners android:radius="12dp"/> + <!--<padding android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp" />--> + </shape> + </item> + <item> + <shape> + <solid android:color="@color/button_normal" /> + <!--<padding android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp" />--> + </shape> + </item> +</selector> diff --git a/platform/android/example/mupdf/src/main/res/layout/annotate_toolbar.xml b/platform/android/example/mupdf/src/main/res/layout/annotate_toolbar.xml new file mode 100644 index 00000000..e0d8ecb4 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/layout/annotate_toolbar.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> + +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + + <HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/toolbar"> + + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/toolbar" + android:paddingTop="10dp" + android:paddingBottom="10dp"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <ImageButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:layout_centerVertical="true" + android:background="@drawable/toolbar_button" + android:id="@+id/save_button" + android:src="@drawable/icon_save" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="@color/black" + android:textSize="11sp" + android:text="SAVE"/> + </LinearLayout> + + <!--a divider--> + <View + android:layout_width="1dp" + android:paddingRight="3dp" android:paddingLeft="3dp" + android:layout_height="match_parent" + android:background="#FF8E8F90" /> + + </LinearLayout> + + </HorizontalScrollView> + +</merge> diff --git a/platform/android/example/mupdf/src/main/res/layout/doc_view.xml b/platform/android/example/mupdf/src/main/res/layout/doc_view.xml new file mode 100644 index 00000000..f1fab51e --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/layout/doc_view.xml @@ -0,0 +1,203 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <TabHost + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight = "0" + android:id="@+id/tabhost"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <View + android:layout_width="fill_parent" + android:layout_height="5dp" + android:background="#1B65BA"> + </View> + + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="#1B65BA"> + + <ImageButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:id="@+id/back_button" + android:src="@drawable/icon_back" + android:tint="@color/white" + android:background="@color/transparent" + android:layout_gravity="center_vertical"/> + + <Space + android:layout_width="30dp" + android:layout_height="1dp"/> + + <TabWidget + android:id="@android:id/tabs" + android:layout_width="300dp" + android:layout_height="match_parent"> + </TabWidget> + + <ImageButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:id="@+id/undo_button" + android:src="@drawable/icon_undo" + android:background="@color/transparent" + android:tint="@color/white" + android:layout_gravity="center_vertical" /> + + <ImageButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:id="@+id/redo_button" + android:src="@drawable/icon_redo" + android:tint="@color/white" + android:background="@color/transparent" + android:layout_gravity="center_vertical" /> + + <ImageButton + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_centerVertical="true" + android:id="@+id/search_button" + android:src="@drawable/icon_find" + android:tint="@color/white" + android:background="@color/transparent" + android:layout_gravity="center_vertical" /> + + </LinearLayout> + + <FrameLayout + android:id="@android:id/tabcontent" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:id="@+id/hiddenTab" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + </LinearLayout> + + <LinearLayout + android:id="@+id/fileTab" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <include layout="@layout/file_toolbar"/> + + </LinearLayout> + + <LinearLayout + android:id="@+id/annotateTab" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/toolbar" + android:orientation="vertical"> + + <include layout="@layout/annotate_toolbar"/> + + </LinearLayout> + + <LinearLayout + android:id="@+id/pagesTab" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/toolbar" + android:orientation="vertical"> + + <include layout="@layout/pages_toolbar"/> + + </LinearLayout> + + <LinearLayout + android:id="@+id/searchTab" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + android:orientation="vertical"> + + <include layout="@layout/search_toolbar"/> + + </LinearLayout> + + </FrameLayout> + </LinearLayout> + </TabHost> + + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_weight = "1" + android:id="@+id/outer_container"> + + <LinearLayout + android:orientation="vertical" + android:layout_width="0dip" + android:layout_weight="@integer/page_width_percentage" + android:layout_height="match_parent" + android:id="@+id/doc_outer_container" + android:background="@color/black"> + + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/doc_wrapper" > + + <com.artifex.mupdf.android.DocView + android:id="@+id/doc_view_inner" + android:layout_width="match_parent" + android:layout_height="match_parent"> + </com.artifex.mupdf.android.DocView> + + </RelativeLayout> + + </LinearLayout> + + <LinearLayout + android:orientation="vertical" + android:layout_width="0dip" + android:layout_weight="@integer/pagelist_width_percentage" + android:layout_height="match_parent" + android:background="#000000" + android:visibility="gone" + android:id="@+id/pages_container" + android:paddingLeft="6dp" + android:paddingRight="6dp"> + </LinearLayout> + + </LinearLayout> + + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight = "0" + android:id="@+id/footer"> + + <TextView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/footer" + android:textColor="@color/footer_text" + android:gravity="center_horizontal" + android:id="@+id/footer_text"/> + + </LinearLayout> + +</LinearLayout> diff --git a/platform/android/example/mupdf/src/main/res/layout/drag_handle.xml b/platform/android/example/mupdf/src/main/res/layout/drag_handle.xml new file mode 100644 index 00000000..a3ca66a2 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/layout/drag_handle.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" > + + <ImageView + android:id="@+id/drag_handle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/icon_selection_hand" + android:padding="15dp" + android:scaleX="0.8" + android:scaleY="0.8" + /> + +</RelativeLayout> diff --git a/platform/android/example/mupdf/src/main/res/layout/file_toolbar.xml b/platform/android/example/mupdf/src/main/res/layout/file_toolbar.xml new file mode 100644 index 00000000..e0d8ecb4 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/layout/file_toolbar.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> + +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + + <HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/toolbar"> + + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/toolbar" + android:paddingTop="10dp" + android:paddingBottom="10dp"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <ImageButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:layout_centerVertical="true" + android:background="@drawable/toolbar_button" + android:id="@+id/save_button" + android:src="@drawable/icon_save" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="@color/black" + android:textSize="11sp" + android:text="SAVE"/> + </LinearLayout> + + <!--a divider--> + <View + android:layout_width="1dp" + android:paddingRight="3dp" android:paddingLeft="3dp" + android:layout_height="match_parent" + android:background="#FF8E8F90" /> + + </LinearLayout> + + </HorizontalScrollView> + +</merge> diff --git a/platform/android/example/mupdf/src/main/res/layout/pages_toolbar.xml b/platform/android/example/mupdf/src/main/res/layout/pages_toolbar.xml new file mode 100644 index 00000000..e0d8ecb4 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/layout/pages_toolbar.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> + +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + + <HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/toolbar"> + + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/toolbar" + android:paddingTop="10dp" + android:paddingBottom="10dp"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <ImageButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:layout_centerVertical="true" + android:background="@drawable/toolbar_button" + android:id="@+id/save_button" + android:src="@drawable/icon_save" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="@color/black" + android:textSize="11sp" + android:text="SAVE"/> + </LinearLayout> + + <!--a divider--> + <View + android:layout_width="1dp" + android:paddingRight="3dp" android:paddingLeft="3dp" + android:layout_height="match_parent" + android:background="#FF8E8F90" /> + + </LinearLayout> + + </HorizontalScrollView> + +</merge> diff --git a/platform/android/example/mupdf/src/main/res/layout/resize_handle.xml b/platform/android/example/mupdf/src/main/res/layout/resize_handle.xml new file mode 100644 index 00000000..443aaad5 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/layout/resize_handle.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" > + + <ImageView + android:id="@+id/drag_handle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/icon_selection_drag_handle" + android:padding="16dp" + android:scaleX="0.8" + android:scaleY="0.8" + /> + +</RelativeLayout> diff --git a/platform/android/example/mupdf/src/main/res/layout/rotate_handle.xml b/platform/android/example/mupdf/src/main/res/layout/rotate_handle.xml new file mode 100644 index 00000000..6a4dadf7 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/layout/rotate_handle.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" > + + <ImageView + android:id="@+id/drag_handle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/icon_selection_rotate" + android:padding="15dp" + android:scaleX="0.8" + android:scaleY="0.8" + /> + +</RelativeLayout> diff --git a/platform/android/example/mupdf/src/main/res/layout/search_toolbar.xml b/platform/android/example/mupdf/src/main/res/layout/search_toolbar.xml new file mode 100644 index 00000000..39afe6d2 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/layout/search_toolbar.xml @@ -0,0 +1,79 @@ +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + + <HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/toolbar"> + + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/toolbar" + android:paddingTop="10dp" + android:paddingBottom="10dp"> + + <Space + android:layout_width="15dp" + android:layout_height="1dp"/> + + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/search_input_wrapper" + android:layout_centerVertical="true" + android:padding="5dp"> + + <Space + android:layout_width="10dp" + android:layout_height="1dp"/> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/device_icon" + android:src="@drawable/icon_find" + /> + + <EditText + android:hint="@string/find" + android:textColorHint="#B3B3B3" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minWidth="400dp" + android:id="@+id/search_text_input" + android:textSize="20sp" + android:background="@drawable/search_text_input" + android:textColor="@color/black" + android:maxLines="1" + android:inputType="text"/> + + </LinearLayout> + + <ImageButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginLeft="5dp" + android:layout_marginRight="5dp" + android:background="@drawable/button" + android:id="@+id/search_next_button" + android:src="@drawable/icon_find_next" /> + + <ImageButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginLeft="5dp" + android:layout_marginRight="5dp" + android:background="@drawable/button" + android:id="@+id/search_previous_button" + android:src="@drawable/icon_find_previous" /> + + </LinearLayout> + + + </HorizontalScrollView> + +</merge> diff --git a/platform/android/example/mupdf/src/main/res/layout/tab.xml b/platform/android/example/mupdf/src/main/res/layout/tab.xml new file mode 100644 index 00000000..9fb91aff --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/layout/tab.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/tabsLayout" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="@drawable/tab_selector" + android:padding="10dip" + android:gravity="center" + android:orientation="vertical"> + + <TextView android:id="@+id/tabText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/title" + android:textSize="15dip" + android:textColor="@drawable/tab_text_selector" /> + +</LinearLayout> diff --git a/platform/android/example/mupdf/src/main/res/layout/tab_left.xml b/platform/android/example/mupdf/src/main/res/layout/tab_left.xml new file mode 100644 index 00000000..e4354528 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/layout/tab_left.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/tabsLayout" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="@drawable/tab_left_selector" + android:padding="10dip" + android:gravity="center" + android:orientation="vertical"> + + <TextView android:id="@+id/tabText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/title" + android:textSize="15dip" + android:textColor="@drawable/tab_text_selector" /> + +</LinearLayout> diff --git a/platform/android/example/mupdf/src/main/res/layout/tab_right.xml b/platform/android/example/mupdf/src/main/res/layout/tab_right.xml new file mode 100644 index 00000000..8d83bbab --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/layout/tab_right.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/tabsLayout" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="@drawable/tab_right_selector" + android:padding="10dip" + android:gravity="center" + android:orientation="vertical"> + + <TextView android:id="@+id/tabText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/title" + android:textSize="15dip" + android:textColor="@drawable/tab_text_selector" /> + +</LinearLayout> diff --git a/platform/android/example/mupdf/src/main/res/values/colors.xml b/platform/android/example/mupdf/src/main/res/values/colors.xml new file mode 100644 index 00000000..79610396 --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/values/colors.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="button_normal">#00000000</color> + <color name="button_selected">#FFBFC1C2</color> + <color name="button_outline">#FF000000</color> + <color name="button_pressed">#FF2572AC</color> + + <color name="toolbar">#DDDFE0</color> + <color name="transparent">#00000000</color> + <color name="my_blue">#FF65FFFF</color> + <color name="white">#FFFFFFFF</color> + <color name="red">#FFFF0000</color> + <color name="black">#000000</color> + <color name="bluish_grey">#43657D</color> + <color name="ltgrey">#CCCCCC</color> + + <color name="sheet_button_normal">#DFDFDF</color> + <color name="sheet_button_plus_normal">#878787</color> + <color name="sheet_button_selected">#2EC0EF</color> + <color name="sheet_button_text_selected">#ffffff</color> + <color name="sheet_button_text">#000000</color> + + <color name="alignment_button_selected">#1176BA</color> + <color name="alignment_dialog_background">#42515F</color> + + <color name="footer">#DFDFDF</color> + <color name="footer_text">#717171</color> + + <color name="breadcrumb_background">#546371</color> + + <color name="formula_popup_background">#42515F</color> + <color name="number_format_popup_background">#42515F</color> + <color name="explorer_blue">#1eb3dc</color> + + <!-- argb overlay color --> + <color name="control_overlay">#44BBFFFF</color> + <!-- rgb version (against white) --> + <color name="control_box">#E7FEFE</color> + + <!-- Slider colors --> + <color name="slider_track_color">#444444</color> + <color name="slider_thumb_color">#0080FF</color> + + <!-- text highlighting --> + <color name="text_highlight_color">#66CCFF</color> + + <color name="blue_dot_color">#0080FF</color> + +</resources> diff --git a/platform/android/example/mupdf/src/main/res/values/integers.xml b/platform/android/example/mupdf/src/main/res/values/integers.xml new file mode 100644 index 00000000..ea63e64a --- /dev/null +++ b/platform/android/example/mupdf/src/main/res/values/integers.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <integer name="page_width_percentage">87</integer> + <integer name="pagelist_width_percentage">13</integer> + + <integer name="text_highlight_alpha">60</integer> + + <integer name="selection_dot_size">20</integer> + <integer name="selection_dot_padding">16</integer> + + <item name="selection_dot_scale" format="float" type="dimen">0.80</item> + +</resources> diff --git a/platform/android/example/mupdf/src/main/res/values/strings.xml b/platform/android/example/mupdf/src/main/res/values/strings.xml index 28af5d41..bee3b319 100644 --- a/platform/android/example/mupdf/src/main/res/values/strings.xml +++ b/platform/android/example/mupdf/src/main/res/values/strings.xml @@ -1,3 +1,11 @@ <resources> <string name="app_name">mupdf</string> + <string name="find">Find</string> + <string name="title">Title</string> + + <string name="hidden_tab">HIDDEN</string> + <string name="file_tab">FILE</string> + <string name="annotate_tab">ANNOTATE</string> + <string name="pages_tab">PAGES</string> + </resources> |