diff options
author | Fred Ross-Perry <fross-perry@conceptuamath.com> | 2016-08-17 17:27:04 -0700 |
---|---|---|
committer | Fred Ross-Perry <fross-perry@conceptuamath.com> | 2016-08-19 11:10:11 -0700 |
commit | db4b628f82239cc5ee7f3759ec23e6e3f0cc9f54 (patch) | |
tree | d9fe8d8cae377cdb11195ae4f498cfcd275060d9 | |
parent | 9f457e6f5b488bc336d5cd337c3bafe2ce32625a (diff) | |
download | mupdf-db4b628f82239cc5ee7f3759ec23e6e3f0cc9f54.tar.xz |
Android example: added text searching.
8 files changed, 362 insertions, 75 deletions
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 index d04a0830..d6b3fbb9 100644 --- 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 @@ -2,13 +2,17 @@ package com.artifex.mupdf.android; import android.app.Activity; import android.content.Context; +import android.graphics.PorterDuff; import android.util.AttributeSet; import android.util.Log; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewTreeObserver; import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.LinearLayout; @@ -34,9 +38,14 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL private String mTagAnnotate; private String mTagPages; - ImageButton mReflowButton; - ImageButton mFirstPageButton; - ImageButton mLastPageButton; + private ImageButton mReflowButton; + private ImageButton mFirstPageButton; + private ImageButton mLastPageButton; + + private ImageButton mSearchButton; + private EditText mSearchText; + private ImageButton mSearchNextButton; + private ImageButton mSearchPreviousButton; public DocActivityView(Context context) { @@ -115,6 +124,14 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL private void showSearchSelected(boolean selected) { + // set the view + mSearchButton.setSelected(selected); + + // colorize + if (selected) + mSearchButton.setColorFilter(0xff000000, PorterDuff.Mode.SRC_IN); + else + mSearchButton.setColorFilter(0xffffffff, PorterDuff.Mode.SRC_IN); } protected void handlePagesTab(String tabId) @@ -281,6 +298,33 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL mLastPageButton = (ImageButton)findViewById(R.id.last_page_button); mLastPageButton.setOnClickListener(this); + mSearchButton = (ImageButton)findViewById(R.id.search_button); + mSearchButton.setOnClickListener(this); + showSearchSelected(false); + mSearchText = (EditText) findViewById(R.id.search_text_input); + mSearchText.setOnClickListener(this); + + // this listener will + mSearchText.setOnEditorActionListener(new TextView.OnEditorActionListener() + { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) + { + if (actionId == EditorInfo.IME_ACTION_NEXT) + { + onSearchNextButton(); + return true; + } + return false; + } + }); + + mSearchNextButton = (ImageButton)findViewById(R.id.search_next_button); + mSearchNextButton.setOnClickListener(this); + + mSearchPreviousButton = (ImageButton)findViewById(R.id.search_previous_button); + mSearchPreviousButton.setOnClickListener(this); + // start the views mDocView.start(path); if (usePagesView()) @@ -363,6 +407,57 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL onFirstPageButton(); if (v == mLastPageButton) onLastPageButton(); + if (v == mSearchButton) + onShowSearch(); + if (v == mSearchText) + onEditSearchText(); + if (v == mSearchNextButton) + onSearchNextButton(); + if (v == mSearchPreviousButton) + onSearchPreviousButton(); + } + + public void onSearchNextButton() + { + hideKeyboard(); + mDocView.onSearchNext(mSearchText.getText().toString()); + } + + public void onSearchPreviousButton() + { + hideKeyboard(); + mDocView.onSearchPrevious(mSearchText.getText().toString()); + } + + public void onEditSearchText() + { + mSearchText.requestFocus(); + showKeyboard(); + } + + public void onShowSearch() + { + // "deselect" all the visible tabs by selecting the hidden (first) one + TabHost tabHost = (TabHost)findViewById(R.id.tabhost); + tabHost.setCurrentTabByTag("HIDDEN"); + + // show search as selected + showSearchSelected(true); + + // hide all the other tabs + hideAllTabs(); + + // show the search tab + findViewById(R.id.searchTab).setVisibility(View.VISIBLE); + mSearchText.getText().clear(); + } + + protected void hideAllTabs() + { + // hide all the other tabs + findViewById(R.id.fileTab).setVisibility(View.GONE); + findViewById(R.id.annotateTab).setVisibility(View.GONE); + findViewById(R.id.pagesTab).setVisibility(View.GONE); } private void onFirstPageButton() 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 a188f90a..50f526f6 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 @@ -53,12 +53,15 @@ public class DocPageView extends View implements Callback private Rect mDisplayRect = new Rect(); private final Paint mPainter; - private final Paint mHighlightPainter; + private final Paint mSelectionHighlightPainter; + private final Paint mSearchHighlightPainter; private final Paint mBlankPainter; private final Paint mDotPainter; private final Rect mSrcRect = new Rect(); private final Rect mDstRect = new Rect(); + private Rect mHighlightingRect = new Rect(); + // cached display lists DisplayList pageContents = null; DisplayList annotContents = null; @@ -77,7 +80,10 @@ public class DocPageView extends View implements Callback private boolean isMostVisible = false; // currently selected TextChars - ArrayList<StructuredText.TextChar> mSelection = null; + private ArrayList<StructuredText.TextChar> mSelection = null; + + // current search hilight + private com.artifex.mupdf.fitz.Rect mSearchHighlight = null; public DocPageView(Context context, Document theDoc) { @@ -88,10 +94,14 @@ public class DocPageView extends View implements Callback 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)); + mSelectionHighlightPainter = new Paint(); + mSelectionHighlightPainter.setColor(ContextCompat.getColor(context, R.color.text_highlight_color)); + mSelectionHighlightPainter.setStyle(Paint.Style.FILL); + mSelectionHighlightPainter.setAlpha(getContext().getResources().getInteger(R.integer.text_highlight_alpha)); + + mSearchHighlightPainter = new Paint(); + mSearchHighlightPainter.setStyle(Paint.Style.STROKE); + mSearchHighlightPainter.setColor(ContextCompat.getColor(context, R.color.black)); mBlankPainter = new Paint(); mBlankPainter.setStyle(Paint.Style.FILL); @@ -420,7 +430,6 @@ public class DocPageView extends View implements Callback StructuredText structuredText = getPage().toStructuredText(); StructuredText.TextBlock textBlocks[] = structuredText.getBlocks(); - com.artifex.mupdf.fitz.Rect r = new com.artifex.mupdf.fitz.Rect(upperLeft.x, upperLeft.y, lowerLeft.x, lowerLeft.y); for (StructuredText.TextBlock block : textBlocks) { for (StructuredText.TextLine line : block.lines) @@ -466,6 +475,12 @@ public class DocPageView extends View implements Callback invalidate(); } + public void setSearchHighlight(com.artifex.mupdf.fitz.Rect r) + { + mSearchHighlight = r; + invalidate(); + } + public void removeSelection() { mSelection = new ArrayList<>(); @@ -475,16 +490,27 @@ public class DocPageView extends View implements Callback @Override public void onDraw(Canvas canvas) { + // clip + canvas.save(); + getLocalVisibleRect(clipRect); + clipPath.reset(); + clipPath.addRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, Path.Direction.CW); + canvas.clipPath(clipPath); + // always start with a blank white background - Rect rBlank = new Rect(); - getLocalVisibleRect(rBlank); - canvas.drawRect(rBlank, mBlankPainter); + canvas.drawRect(clipRect, mBlankPainter); if (mFinished) + { + canvas.restore(); return; + } if (mDrawBitmap == null) + { + canvas.restore(); return; // not yet rendered + } // set rectangles for drawing mSrcRect.set(mDrawSrcRect); @@ -500,13 +526,6 @@ public class DocPageView extends View implements Callback 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); @@ -515,12 +534,22 @@ public class DocPageView extends View implements Callback { for (StructuredText.TextChar tchar : mSelection) { - Rect r = new Rect((int) tchar.bbox.x0, (int) tchar.bbox.y0, (int) tchar.bbox.x1, (int) tchar.bbox.y1); - Rect r2 = pageToView(r); - canvas.drawRect(r2, mHighlightPainter); + mHighlightingRect.set((int) tchar.bbox.x0, (int) tchar.bbox.y0, (int) tchar.bbox.x1, (int) tchar.bbox.y1); + pageToView(mHighlightingRect, mHighlightingRect); + canvas.drawRect(mHighlightingRect, mSelectionHighlightPainter); } } + // search highlight. Same as the selection highlight, but with + // a thin black outline. + if (mSearchHighlight != null) + { + mHighlightingRect.set((int) mSearchHighlight.x0, (int) mSearchHighlight.y0, (int) mSearchHighlight.x1, (int) mSearchHighlight.y1); + pageToView(mHighlightingRect, mHighlightingRect); + canvas.drawRect(mHighlightingRect, mSelectionHighlightPainter); + canvas.drawRect(mHighlightingRect, mSearchHighlightPainter); + } + // draw blue dot if (isMostVisible) { @@ -673,7 +702,7 @@ public class DocPageView extends View implements Callback return new Point(viewX, viewY); } - public Rect pageToView(Rect pageR) + public void pageToView(Rect pageR, Rect viewR) { double factor = getFactor(); @@ -682,7 +711,7 @@ public class DocPageView extends View implements Callback int right = (int) (((double) pageR.right) * factor); int bottom = (int) (((double) pageR.bottom) * factor); - return new Rect(left, top, right, bottom); + viewR.set(left, top, right, bottom); } public Point viewToPage(int viewX, int viewY) 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 00e3e14c..346b887b 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 @@ -10,6 +10,7 @@ import android.util.TypedValue; import android.view.View; import android.widget.RelativeLayout; +import com.artifex.mupdf.fitz.Page; import com.artifex.mupdf.fitz.R; public class DocView extends DocViewBase implements DragHandleListener @@ -28,6 +29,16 @@ public class DocView extends DocViewBase implements DragHandleListener DocPageView selectionEndPage = null; Point selectionEndLoc = new Point(); + // searching + // what we're searching for + private String mSearchNeedle = ""; + // current page we're searching + private int mSearchPage = 0; + // array of matching rects + private com.artifex.mupdf.fitz.Rect mSearchRects[] = null; + // index into the array + private int mSearchIndex = -1; + public DocView(Context context) { super(context); @@ -267,4 +278,143 @@ public class DocView extends DocViewBase implements DragHandleListener } } } + + // clear the selection on all pages + private void removeSearchHighlights() + { + int numPages = getPageCount(); + for (int i = 0; i < numPages; i++) + { + DocPageView cv = (DocPageView) getOrCreateChild(i); + cv.setSearchHighlight(null); + } + } + + // change what's being searched for + private void setNeedle(String needle) + { + if (!needle.equals(mSearchNeedle)) + { + mSearchNeedle = needle; + mSearchRects = null; + } + } + + public void onSearchNext(String needle) + { + setNeedle(needle); + removeSearchHighlights(); + + if (doSearch(1, mSearchPage)) + { + DocPageView dpv = (DocPageView)getOrCreateChild(mSearchPage); + dpv.setSearchHighlight(mSearchRects[mSearchIndex]); + scrollRectIntoView(mSearchPage, mSearchRects[mSearchIndex]); + } + } + + public void onSearchPrevious(String needle) + { + setNeedle(needle); + removeSearchHighlights(); + + if (doSearch(-1, mSearchPage)) + { + DocPageView dpv = (DocPageView)getOrCreateChild(mSearchPage); + dpv.setSearchHighlight(mSearchRects[mSearchIndex]); + scrollRectIntoView(mSearchPage, mSearchRects[mSearchIndex]); + } + } + + // performs a search for the next match. + // direction - 1 for forward, -1 for backward + // startPage - used as a test for circularity. + + private boolean doSearch(int direction, int startPage) + { + if (mSearchRects == null) + { + // see if we have matches for this page + DocPageView dpv = (DocPageView)getOrCreateChild(mSearchPage); + Page page = dpv.getPage(); + mSearchRects = page.search(mSearchNeedle); + + if (mSearchRects!=null && mSearchRects.length>0) + { + // matches found, return the first one + if (direction>0) + mSearchIndex = 0; + else + mSearchIndex = mSearchRects.length-1; + return true; + } + } + + // look forward or backward for the next match + if (mSearchRects!=null && mSearchRects.length>0) + { + if (direction>0) + { + if (mSearchIndex+1 < mSearchRects.length && mSearchIndex+1 >= 0) + { + mSearchIndex++; + return true; + } + } + else + { + if (mSearchIndex-1 < mSearchRects.length && mSearchIndex-1 >= 0) + { + mSearchIndex--; + return true; + } + } + } + + // no more matches on this page, go to the next (or previous) page + if (direction>0) + { + mSearchPage++; + if (mSearchPage >= getPageCount()) + mSearchPage = 0; + } + else + { + mSearchPage--; + if (mSearchPage < 0) + mSearchPage = getPageCount()-1; + } + mSearchRects = null; + + // give up if we're still looking + if (mSearchPage == startPage) + return false; + + // look on the next page + return doSearch(direction, startPage); + } + + public void scrollRectIntoView(int pageNum, com.artifex.mupdf.fitz.Rect 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.x0, (int) box.y1); + 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); + } + } + } 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 index c271cad4..9c8ab998 100755 --- 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 @@ -4,7 +4,6 @@ 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; @@ -995,29 +994,6 @@ public class DocViewBase 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; 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 index 924e3fe8..125d8559 100644 --- 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 @@ -2,7 +2,7 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/white" /> - <stroke android:width="1dp" android:color="#888888"/> + <stroke android:width="3dp" android:color="#888888"/> <corners android:bottomRightRadius="24dp" android:bottomLeftRadius="24dp" 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 index dec1313f..452d0b28 100644 --- a/platform/android/example/mupdf/src/main/res/layout/doc_view.xml +++ b/platform/android/example/mupdf/src/main/res/layout/doc_view.xml @@ -73,9 +73,10 @@ 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" /> + android:background="@drawable/search_button" + android:layout_gravity="center_vertical" + android:paddingLeft="10dp" + android:paddingRight="12dp"/> </LinearLayout> 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 index 39afe6d2..973639bc 100644 --- a/platform/android/example/mupdf/src/main/res/layout/search_toolbar.xml +++ b/platform/android/example/mupdf/src/main/res/layout/search_toolbar.xml @@ -11,7 +11,9 @@ android:layout_height="match_parent" android:background="@color/toolbar" android:paddingTop="10dp" - android:paddingBottom="10dp"> + android:paddingBottom="10dp" + android:gravity="center_vertical" + > <Space android:layout_width="15dp" @@ -22,54 +24,85 @@ 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_width="5dp" android:layout_height="1dp"/> <ImageView - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="40dp" + android:layout_height="40dp" android:id="@+id/device_icon" android:src="@drawable/icon_find" + android:scaleType="centerCrop" /> <EditText android:hint="@string/find" android:textColorHint="#B3B3B3" android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_height="fill_parent" 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"/> + android:inputType="text" + /> </LinearLayout> - <ImageButton + <LinearLayout 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:orientation="vertical" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:gravity="center" + > + + <ImageButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:background="@drawable/toolbar_button" + android:id="@+id/search_next_button" + android:src="@drawable/icon_find_next" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/black" + android:textSize="11sp" + android:text="@string/next_upper"/> + </LinearLayout> + + <LinearLayout 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" /> + android:orientation="vertical" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:gravity="center" + > + + <ImageButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:background="@drawable/toolbar_button" + android:id="@+id/search_previous_button" + android:src="@drawable/icon_find_previous" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/black" + android:textSize="11sp" + android:text="@string/previous_upper"/> + </LinearLayout> </LinearLayout> 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 bee3b319..be1a518a 100644 --- a/platform/android/example/mupdf/src/main/res/values/strings.xml +++ b/platform/android/example/mupdf/src/main/res/values/strings.xml @@ -8,4 +8,7 @@ <string name="annotate_tab">ANNOTATE</string> <string name="pages_tab">PAGES</string> + <string name="next_upper">NEXT</string> + <string name="previous_upper">PREVIOUS</string> + </resources> |