summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
authorfred ross-perry <fredross-perry@Fred-Ross-Perrys-Computer.local>2016-08-26 09:50:48 -0700
committerfred ross-perry <fredross-perry@Fred-Ross-Perrys-Computer.local>2016-09-14 08:53:32 -0700
commitffbe3db71ea0f96b408e22418547a8ff898f380e (patch)
tree2f34407ef3c229a949b0a2b514e307297437a169 /platform
parente18d11b63af0ca8a302f23b32ffc24578c830989 (diff)
downloadmupdf-ffbe3db71ea0f96b408e22418547a8ff898f380e.tar.xz
Android example - drawing ink annotations
This commit puts in the UI for drawing with color and line thickness. But it does not yet save this to the document.
Diffstat (limited to 'platform')
-rw-r--r--platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/ColorDialog.java204
-rw-r--r--platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocActivityView.java126
-rwxr-xr-xplatform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java371
-rw-r--r--platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java227
-rw-r--r--platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/LineWidthDialog.java128
-rw-r--r--platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/ToolbarButton.java31
-rw-r--r--platform/android/example/mupdf/src/main/res/drawable/colors.xml13
-rwxr-xr-xplatform/android/example/mupdf/src/main/res/drawable/icon_delete.xml20
-rw-r--r--platform/android/example/mupdf/src/main/res/drawable/icon_draw.xml26
-rwxr-xr-xplatform/android/example/mupdf/src/main/res/drawable/icon_highlight.xml21
-rwxr-xr-xplatform/android/example/mupdf/src/main/res/drawable/icon_line_color.xml14
-rwxr-xr-xplatform/android/example/mupdf/src/main/res/drawable/icon_line_thickness.xml14
-rwxr-xr-xplatform/android/example/mupdf/src/main/res/drawable/icon_note.xml30
-rw-r--r--platform/android/example/mupdf/src/main/res/drawable/number_format_popup.xml13
-rw-r--r--platform/android/example/mupdf/src/main/res/drawable/toolbar_button2.xml17
-rw-r--r--platform/android/example/mupdf/src/main/res/drawable/transparent_color_swatch.xml22
-rw-r--r--platform/android/example/mupdf/src/main/res/drawable/wheel_bg.xml29
-rw-r--r--platform/android/example/mupdf/src/main/res/drawable/wheel_val.xml27
-rw-r--r--platform/android/example/mupdf/src/main/res/layout/annotate_toolbar.xml223
-rw-r--r--platform/android/example/mupdf/src/main/res/layout/colors.xml95
-rw-r--r--platform/android/example/mupdf/src/main/res/layout/file_toolbar.xml10
-rw-r--r--platform/android/example/mupdf/src/main/res/layout/line_width_dialog.xml15
-rw-r--r--platform/android/example/mupdf/src/main/res/layout/line_width_item.xml25
-rw-r--r--platform/android/example/mupdf/src/main/res/layout/pages_toolbar.xml6
-rw-r--r--platform/android/example/mupdf/src/main/res/layout/search_toolbar.xml4
-rw-r--r--platform/android/example/mupdf/src/main/res/values/strings.xml3
-rw-r--r--platform/android/example/mupdf/src/main/res/values/styles.xml11
-rw-r--r--platform/java/src/kankan/wheel/widget/ItemsRange.java81
-rw-r--r--platform/java/src/kankan/wheel/widget/OnWheelChangedListener.java33
-rw-r--r--platform/java/src/kankan/wheel/widget/OnWheelClickedListener.java32
-rw-r--r--platform/java/src/kankan/wheel/widget/OnWheelScrollListener.java34
-rw-r--r--platform/java/src/kankan/wheel/widget/WheelAdapter.java46
-rw-r--r--platform/java/src/kankan/wheel/widget/WheelRecycle.java153
-rw-r--r--platform/java/src/kankan/wheel/widget/WheelScroller.java252
-rw-r--r--platform/java/src/kankan/wheel/widget/WheelView.java889
-rw-r--r--platform/java/src/kankan/wheel/widget/adapters/AbstractWheelAdapter.java74
-rw-r--r--platform/java/src/kankan/wheel/widget/adapters/AbstractWheelTextAdapter.java268
-rw-r--r--platform/java/src/kankan/wheel/widget/adapters/AdapterWheel.java62
-rw-r--r--platform/java/src/kankan/wheel/widget/adapters/ArrayWheelAdapter.java62
-rw-r--r--platform/java/src/kankan/wheel/widget/adapters/ArrayWheelAdapterColor.java50
-rw-r--r--platform/java/src/kankan/wheel/widget/adapters/NumericWheelAdapter.java85
-rw-r--r--platform/java/src/kankan/wheel/widget/adapters/WheelViewAdapter.java64
42 files changed, 3875 insertions, 35 deletions
diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/ColorDialog.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/ColorDialog.java
new file mode 100644
index 00000000..5da38c2e
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/ColorDialog.java
@@ -0,0 +1,204 @@
+package com.artifex.mupdf.android;
+
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import com.artifex.mupdf.fitz.R;
+
+public class ColorDialog implements View.OnTouchListener, PopupWindow.OnDismissListener {
+
+ // these are set in the constructor.
+ private final Context mContext;
+ private final View mAnchor;
+ private final ColorChangedListener mListener;
+ private final int mDialogType;
+ private boolean mAutoDismiss = false;
+
+ // only one ColorDialog at a time please.
+ private static ColorDialog singleton = null;
+
+ // a popup window for us.
+ private PopupWindow popupWindow;
+
+ // two different variants of this dilog
+ public static final int FG_COLORS = 1;
+ public static final int BG_COLORS = 2;
+
+ // allowable foreground colors. These come from UE2FileViewerApp
+ private final String mFgColors[] = {"#000000", "#FFFFFF", "#D8D8D8", "#808080", "#EEECE1", "#1F497D",
+ "#0070C0", "#C0504D", "#9BBB59", "#8064A2", "#4BACC6", "#F79646", "#FF0000",
+ "#FFFF00", "#DBE5F1", "#F2DCDB", "#EBF1DD", "#00B050"};
+
+ // constructor
+ public ColorDialog(int dialogType, Context context, View anchor, ColorChangedListener listener, boolean autoDismiss)
+ {
+ mContext = context;
+ mAnchor = anchor;
+ mListener = listener;
+ mDialogType = dialogType;
+ mAutoDismiss = autoDismiss;
+ }
+
+ // whether or not to show the title
+ private boolean mShowTitle = true;
+ public void setShowTitle(boolean val) {mShowTitle = val;}
+
+ // time to show the dialog
+ public void show()
+ {
+ // remember us
+ singleton = this;
+
+ // get the layout
+ View popupView = LayoutInflater.from(mContext).inflate(R.layout.colors, null);
+
+ // set the title
+ TextView tv = (TextView)popupView.findViewById(R.id.color_dialog_title);
+ if (mShowTitle) {
+ if (mDialogType == BG_COLORS)
+ tv.setText(mContext.getString(R.string.background));
+ else
+ tv.setText(mContext.getString(R.string.color));
+ }
+ else {
+ tv.setVisibility(View.GONE);
+ }
+
+ // choose the list of colors to use
+ String colors[] = mFgColors;
+
+ // get the three rows of buttons from the layout
+ LinearLayout rows[] = new LinearLayout[3];
+ rows[0] = (LinearLayout)popupView.findViewById(R.id.fontcolors_row1);
+ rows[1] = (LinearLayout)popupView.findViewById(R.id.fontcolors_row2);
+ rows[2] = (LinearLayout)popupView.findViewById(R.id.fontcolors_row3);
+
+ // Set up a button for each color.
+ // Hide buttons for which there are no colors.
+ int icolor = 0;
+ int irow;
+ for (irow=0; irow<rows.length; irow++) {
+ LinearLayout row = rows[irow];
+ int count = row.getChildCount();
+ for (int i=0; i<count; i++) {
+ Button button = (Button)row.getChildAt(i);
+ if (icolor+1 <= colors.length) {
+ button.setVisibility(View.VISIBLE);
+ button.setBackgroundColor(Color.parseColor(colors[icolor]));
+ button.setTag(colors[icolor]);
+ button.setOnClickListener(new Button.OnClickListener() {
+ @Override
+ public void onClick(View v)
+ {
+ mListener.onColorChanged((String)v.getTag());
+ if (mAutoDismiss)
+ dismiss();
+ }
+ });
+ }
+ else {
+ button.setVisibility(View.GONE);
+ }
+ icolor++;
+ }
+ }
+
+ // Set up the transaparent button if we're doing background colors.
+ Button tpb = (Button) popupView.findViewById(R.id.transparent_color_button);
+ if (mDialogType==ColorDialog.BG_COLORS) {
+ tpb.setVisibility(View.VISIBLE);
+ tpb.setOnClickListener(new Button.OnClickListener() {
+ @Override
+ public void onClick(View v)
+ {
+ mListener.onColorChanged((String)v.getTag());
+ }
+ });
+ }
+ else
+ tpb.setVisibility(View.GONE);
+
+ // get screen width
+ WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics metrics = new DisplayMetrics();
+ wm.getDefaultDisplay().getMetrics(metrics);
+ int screenWidth = metrics.widthPixels;
+
+ // put everything in a popup window and show it.
+ // by default that's the upper right corner, but the dialog is draggable.
+ popupWindow = new PopupWindow(popupView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ popupWindow.setFocusable(true);
+ popupView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ popupWindow.showAtLocation(mAnchor, Gravity.TOP|Gravity.LEFT, screenWidth-popupView.getMeasuredWidth()-15, 100);
+ popupWindow.setClippingEnabled(false);
+ // this enables dragging
+ popupView.setOnTouchListener(this);
+ // this allows us to know when the popup is being dismissed
+ popupWindow.setOnDismissListener(this);
+ }
+
+ // internal function to dismiss the popup.
+ public void dismiss()
+ {
+ popupWindow.dismiss();
+ singleton = null;
+ }
+
+ // static function to dismiss the popup.
+ static public void finish()
+ {
+ if (singleton!=null)
+ singleton.dismiss();
+ }
+
+ // this function is called when the user taps outside the popup.
+ // we make sure to dismiss it properly.
+ @Override
+ public void onDismiss() {
+ dismiss();
+ }
+
+ // we're an onTouch listener for the popup window.
+ // we use that to allow for dragging the popup around.
+
+ private int start[];
+ private final Point down = new Point();
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+
+ start = new int[2];
+ popupWindow.getContentView().getLocationOnScreen(start);
+ down.set((int) event.getRawX(), (int) event.getRawY());
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+
+ int dx = down.x - (int)event.getRawX();
+ int dy = down.y - (int)event.getRawY();
+ popupWindow.update(start[0]-dx, start[1]-dy,-1, -1, true);
+ break;
+ }
+ return true;
+ }
+
+ public interface ColorChangedListener {
+ void onColorChanged(String color);
+ }
+
+}
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 e56ed43d..26624ab5 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,6 +2,7 @@ package com.artifex.mupdf.android;
import android.app.Activity;
import android.content.Context;
+import android.graphics.Color;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.util.Log;
@@ -26,7 +27,7 @@ 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, View.OnClickListener
+public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeListener, View.OnClickListener, DocView.SelectionChangeListener
{
private DocView mDocView;
private DocReflowView mDocReflowView;
@@ -55,7 +56,15 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL
private ImageButton mPrintButton;
private ImageButton mShareButton;
private ImageButton mOpenInButton;
+
private ImageButton mToggleAnnotButton;
+ private ImageButton mHighlightButton;
+ private ImageButton mDeleteButton;
+
+ private ImageButton mNoteButton;
+ private ImageButton mDrawButton;
+ private ImageButton mLineColorButton;
+ private ImageButton mLineThicknessButton;
public DocActivityView(Context context)
{
@@ -356,6 +365,23 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL
mToggleAnnotButton = (ImageButton)findViewById(R.id.show_annot_button);
mToggleAnnotButton.setOnClickListener(this);
+ mHighlightButton = (ImageButton)findViewById(R.id.highlight_button);
+ mHighlightButton.setOnClickListener(this);
+
+ mNoteButton = (ImageButton)findViewById(R.id.note_button);
+ mNoteButton.setOnClickListener(this);
+
+ mDrawButton = (ImageButton)findViewById(R.id.draw_button);
+ mDrawButton.setOnClickListener(this);
+
+ mLineColorButton = (ImageButton)findViewById(R.id.line_color_button);
+ mLineColorButton.setOnClickListener(this);
+
+ mLineThicknessButton = (ImageButton)findViewById(R.id.line_thickness_button);
+ mLineThicknessButton.setOnClickListener(this);
+
+ mDeleteButton = (ImageButton)findViewById(R.id.delete_button);
+ mDeleteButton.setOnClickListener(this);
mDoc = new Document(path);
@@ -409,6 +435,10 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL
{
mDocPagesView.clone(mDocView);
}
+
+ mHighlightButton.setEnabled(false);
+ mDocView.setSelectionChangeListener(this);
+ onSelectionChanged();
}
public void showUI(boolean show)
@@ -481,10 +511,12 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL
{
if (v == mReflowButton)
onReflowButton();
+
if (v == mFirstPageButton)
onFirstPageButton();
if (v == mLastPageButton)
onLastPageButton();
+
if (v == mSearchButton)
onShowSearch();
if (v == mSearchText)
@@ -493,6 +525,7 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL
onSearchNextButton();
if (v == mSearchPreviousButton)
onSearchPreviousButton();
+
if (v == mBackButton)
onBackButton();
@@ -506,8 +539,22 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL
onShareButton();
if (v == mOpenInButton)
onOpenInButton();
+
if (v == mToggleAnnotButton)
onToggleAnnotButton();
+ if (v == mHighlightButton)
+ onHighlightButton();
+ if (v == mDeleteButton)
+ onDeleteButton();
+
+ if (v == mNoteButton)
+ onNoteButton();
+ if (v == mDrawButton)
+ onDrawButton();
+ if (v == mLineColorButton)
+ onLineColorButton();
+ if (v == mLineThicknessButton)
+ onLineThicknessButton();
}
public void onSearchNextButton()
@@ -647,6 +694,83 @@ public class DocActivityView extends FrameLayout implements TabHost.OnTabChangeL
mDocView.toggleAnnotations();
}
+ private void onHighlightButton()
+ {
+ mDocView.onHighlight();
+ }
+
+ private void onNoteButton()
+ {
+ mDocView.onNoteMode();
+ }
+
+ private void onDrawButton()
+ {
+ mDocView.onDrawMode();
+ }
+
+ private void onLineColorButton()
+ {
+ if (mDocView.getDrawMode() || mDocView.hasInkAnnotationSelected())
+ {
+ ColorDialog dlg = new ColorDialog(ColorDialog.BG_COLORS,
+ getContext(), mLineColorButton, new ColorDialog.ColorChangedListener()
+ {
+ @Override
+ public void onColorChanged(String color)
+ {
+ int icolor = Color.parseColor(color);
+ mDocView.setInkLineColor(icolor);
+ mLineColorButton.setColorFilter(icolor, PorterDuff.Mode.SRC_IN);
+ }
+ }, true);
+ dlg.setShowTitle(false);
+ dlg.show();
+ }
+ }
+
+ private void onLineThicknessButton()
+ {
+ if (mDocView.getDrawMode() || mDocView.hasInkAnnotationSelected())
+ {
+ float val = mDocView.getInkLineThickness();
+ LineWidthDialog.show(getContext(), mLineThicknessButton, val,
+ new LineWidthDialog.WidthChangedListener()
+ {
+ @Override
+ public void onWidthChanged(float value)
+ {
+ mDocView.setInkLineThickness(value);
+ }
+ });
+ }
+ }
+
+ private void onDeleteButton()
+ {
+ mDocView.onDelete();
+ }
+
+ public void onSelectionChanged()
+ {
+ boolean hasSel = mDocView.hasSelection();
+ boolean hasInkAnnotSel = mDocView.hasInkAnnotationSelected();
+
+ mHighlightButton.setEnabled(hasSel);
+
+ boolean noteMode = mDocView.getNoteMode();
+ mNoteButton.setSelected(noteMode);
+ findViewById(R.id.note_holder).setSelected(noteMode);
+
+ boolean drawMode = mDocView.getDrawMode();
+ mDrawButton.setSelected(drawMode);
+ mLineColorButton.setEnabled(drawMode || hasInkAnnotSel);
+ mLineThicknessButton.setEnabled(drawMode || hasInkAnnotSel);
+ mDeleteButton.setEnabled(!drawMode && hasInkAnnotSel);
+
+ findViewById(R.id.draw_tools_holder).setSelected(drawMode);
+ }
+
private OnDoneListener mDoneListener = null;
public void setOnDoneListener(OnDoneListener l) {mDoneListener = l;}
public interface OnDoneListener
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 50f526f6..0bde1f03 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
@@ -7,10 +7,13 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.AsyncTask;
import android.support.v4.content.ContextCompat;
import android.util.Log;
+import android.util.TypedValue;
import android.view.KeyEvent.Callback;
import android.view.View;
import android.view.ViewGroup;
@@ -27,6 +30,8 @@ import com.artifex.mupdf.fitz.StructuredText;
import com.artifex.mupdf.fitz.android.AndroidDrawDevice;
import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.ListIterator;
public class DocPageView extends View implements Callback
{
@@ -550,6 +555,17 @@ public class DocPageView extends View implements Callback
canvas.drawRect(mHighlightingRect, mSearchHighlightPainter);
}
+ // draw ink annotations
+ if (mInkAnnots != null)
+ {
+ Iterator<InkAnnotation> it = mInkAnnots.iterator();
+ while (it.hasNext())
+ {
+ InkAnnotation annot = it.next();
+ annot.draw(canvas);
+ }
+ }
+
// draw blue dot
if (isMostVisible)
{
@@ -668,11 +684,24 @@ public class DocPageView extends View implements Callback
return screenToPage(p.x, p.y);
}
+ public PointF screenToPage(PointF p)
+ {
+ Point pp = screenToPage(new Point((int)p.x, (int)p.y));
+ return new PointF(pp.x, pp.y);
+ }
+
private double getFactor()
{
return mZoom * mScale * mResolution / 72f;
}
+ private double pageToScreen(double val)
+ {
+ double factor = getFactor();
+
+ return factor * val;
+ }
+
private Point screenToPage(int screenX, int screenY)
{
// convert to view-relative
@@ -702,6 +731,36 @@ public class DocPageView extends View implements Callback
return new Point(viewX, viewY);
}
+ public void pageToView(Point pageP, Point viewP)
+ {
+ double factor = getFactor();
+
+ int x = (int) (((double) pageP.x) * factor);
+ int y = (int) (((double) pageP.y) * factor);
+
+ viewP.set(x, y);
+ }
+
+ public void pageToView(PointF pageP, PointF viewP)
+ {
+ double factor = getFactor();
+
+ float x = (pageP.x * (float)factor);
+ float y = (pageP.y * (float)factor);
+
+ viewP.set(x, y);
+ }
+
+ private PointF pageToView(PointF pageP)
+ {
+ double factor = getFactor();
+
+ float x = (pageP.x * (float)factor);
+ float y = (pageP.y * (float)factor);
+
+ return new PointF(x, y);
+ }
+
public void pageToView(Rect pageR, Rect viewR)
{
double factor = getFactor();
@@ -751,6 +810,35 @@ public class DocPageView extends View implements Callback
public boolean onSingleTap(int x, int y)
{
+ // see if an ink annotation has been tapped on
+ if (mInkAnnots != null)
+ {
+ boolean hit = false;
+
+ // switch to page coordinates
+ Point pt = screenToPage(x, y);
+
+ // iterate in reverse order
+ ListIterator<InkAnnotation> li = mInkAnnots.listIterator(mInkAnnots.size());
+ while (li.hasPrevious())
+ {
+ InkAnnotation annot = li.previous();
+ annot.setSelected(false);
+ if(annot.hitTest(pt))
+ {
+ if (!hit)
+ annot.setSelected(true);
+ hit = true;
+ }
+ }
+
+ if (hit)
+ {
+ invalidate();
+ return true;
+ }
+ }
+
// NOTE: when double-tapping, a single-tap will also happen first.
// so that must be safe to do.
@@ -765,17 +853,53 @@ public class DocPageView extends View implements Callback
// 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 ArrayList<InkAnnotation> mInkAnnots;
+
+ public void startDraw(float x, float y, int color, float thickness)
+ {
+ // create annotation list
+ if (mInkAnnots == null)
+ mInkAnnots = new ArrayList<>();
+
+ // add a new annotation to the list
+ // convert thickness from pt to pixels
+ float px = thickness * (float)getFactor();
+ InkAnnotation annot = new InkAnnotation(color, px);
+ mInkAnnots.add(annot);
+
+ // add first point to the new annot, in page coords
+ PointF pScreen = new PointF(x, y);
+ PointF pPage = screenToPage(pScreen);
+ annot.add(pPage);
+
+ invalidate();
+ }
+
+ public void continueDraw(float x, float y)
+ {
+ if (mInkAnnots!=null && mInkAnnots.size()>0)
+ {
+ // get the most recent annotation
+ InkAnnotation annot = mInkAnnots.get(mInkAnnots.size()-1);
+
+ // add the point, in page coords
+ PointF pScreen = new PointF(x, y);
+ PointF pPage = screenToPage(pScreen);
+ annot.add(pPage);
+
+ invalidate();
+ }
+ }
+
private class RenderTaskParams
{
RenderTaskParams(RenderListener listener, Matrix ctm, Bitmap bitmap,
@@ -865,4 +989,247 @@ public class DocPageView extends View implements Callback
}
}
+ private InkAnnotation getSelectedInkAnnotation()
+ {
+ if (mInkAnnots != null)
+ {
+ // iterate in reverse order
+ ListIterator<InkAnnotation> li = mInkAnnots.listIterator(mInkAnnots.size());
+ while (li.hasPrevious())
+ {
+ InkAnnotation annot = li.previous();
+ if (annot.isSelected())
+ return annot;
+ }
+ }
+
+ return null;
+ }
+
+ public boolean hasInkAnnotationSelected()
+ {
+ return (getSelectedInkAnnotation() != null);
+ }
+
+ public void setSelectedInkLineColor(int val)
+ {
+ InkAnnotation annot = getSelectedInkAnnotation();
+ if (annot != null)
+ {
+ annot.setLineColor(val);
+ invalidate();
+ }
+ }
+
+ public void setSelectedInkLineThickness(float val)
+ {
+ InkAnnotation annot = getSelectedInkAnnotation();
+ if (annot != null)
+ {
+ float px = val * (float)getFactor();
+ annot.setLineThickness(px);
+ invalidate();
+ }
+ }
+
+ public void deleteSelectedInkAnnotation()
+ {
+ InkAnnotation annot = getSelectedInkAnnotation();
+ if (annot != null)
+ {
+ mInkAnnots.remove(annot);
+ invalidate();
+ }
+ }
+
+//-----------------------------------------------------
+
+ public class InkAnnotation
+ {
+ private float mLineThickness;
+ public void setLineThickness(float lineThickness) {mLineThickness = lineThickness;}
+
+ private int mLineColor;
+ public void setLineColor(int lineColor) {mLineColor = lineColor;}
+
+ private boolean mSelected = false;
+ public void setSelected(boolean sel) {mSelected = sel;}
+ public boolean isSelected() {return mSelected;}
+
+ private ArrayList<PointF> mArc;
+
+ // touch margin is 2mm either side of the arc
+ private final float HIT_MARGIN = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 2,
+ getResources().getDisplayMetrics());
+
+ public InkAnnotation(int lineColor, float lineThickness)
+ {
+ mLineColor = lineColor;
+ mLineThickness = lineThickness;
+ mArc = new ArrayList<>();
+ }
+
+ public void add(PointF p)
+ {
+ mArc.add(p);
+ }
+
+ public boolean hitTest(Point pt)
+ {
+ PointF p1, p2;
+ PointF ptf = new PointF(pt);
+
+ if (mArc.size() >= 2)
+ {
+ Iterator<PointF> iit = mArc.iterator();
+ p1 = iit.next();
+ while (iit.hasNext())
+ {
+ p2 = iit.next();
+
+ // test
+ double d = LineToPointDistance2D(p1, p2, ptf);
+ d = pageToScreen(d);
+
+ if (d <= HIT_MARGIN)
+ {
+ return true;
+ }
+
+ p1 = p2;
+ }
+ }
+ else
+ {
+ p1 = mArc.get(0);
+
+ // test
+ double d = Distance(p1, ptf);
+ d = pageToScreen(d);
+
+ if (d <= HIT_MARGIN)
+ {
+ return true;
+ }
+
+ }
+
+ return false;
+ }
+
+ public void draw(Canvas canvas)
+ {
+ Path path = new Path();
+ PointF pPage;
+ PointF pView = new PointF();
+
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setDither(true);
+ paint.setStrokeJoin(Paint.Join.ROUND);
+ paint.setStrokeCap(Paint.Cap.ROUND);
+ paint.setStyle(Paint.Style.FILL);
+ paint.setStrokeWidth(mLineThickness * mScale);
+ paint.setColor(mLineColor);
+
+ RectF bounds = new RectF();
+
+ if (mArc.size() >= 2)
+ {
+ Iterator<PointF> iit = mArc.iterator();
+ pPage = iit.next();
+ pageToView(pPage, pView);
+ float mX = pView.x;
+ float mY = pView.y;
+ path.moveTo(mX, mY);
+ while (iit.hasNext())
+ {
+ pPage = iit.next();
+ pageToView(pPage, pView);
+ float x = pView.x;
+ float y = pView.y;
+ path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
+ mX = x;
+ mY = y;
+ }
+ path.lineTo(mX, mY);
+ paint.setStyle(Paint.Style.STROKE);
+ canvas.drawPath(path, paint);
+
+ path.computeBounds(bounds, true);
+ }
+ else
+ {
+ pPage = mArc.get(0);
+ pageToView(pPage, pView);
+ float r = mLineThickness * mScale / 2;
+ bounds.set(pView.x-r, pView.y-r, pView.x+r, pView.y+r);
+ canvas.drawCircle(pView.x, pView.y, r, paint);
+ }
+
+ if (isSelected())
+ {
+ // expand the bounds to account for ine thickness
+ float px = mLineThickness * mScale / 2;
+ bounds.inset(-px, -px);
+
+ mHighlightingRect.set((int)bounds.left, (int)bounds.top, (int)bounds.right, (int)bounds.bottom);
+ canvas.drawRect(mHighlightingRect, mSelectionHighlightPainter);
+ }
+ }
+
+ private double DotProduct(PointF pointA, PointF pointB, PointF pointC)
+ {
+ double[] AB = new double[2];
+ double[] BC = new double[2];
+ AB[0] = pointB.x - pointA.x;
+ AB[1] = pointB.y - pointA.y;
+ BC[0] = pointC.x - pointB.x;
+ BC[1] = pointC.y - pointB.y;
+ double dot = AB[0] * BC[0] + AB[1] * BC[1];
+
+ return dot;
+ }
+
+ //Compute the cross product AB x AC
+ private double CrossProduct(PointF pointA, PointF pointB, PointF pointC)
+ {
+ double[] AB = new double[2];
+ double[] AC = new double[2];
+ AB[0] = pointB.x - pointA.x;
+ AB[1] = pointB.y - pointA.y;
+ AC[0] = pointC.x - pointA.x;
+ AC[1] = pointC.y - pointA.y;
+ double cross = AB[0] * AC[1] - AB[1] * AC[0];
+
+ return cross;
+ }
+
+ //Compute the distance from A to B
+ double Distance(PointF pointA, PointF pointB)
+ {
+ double d1 = pointA.x - pointB.x;
+ double d2 = pointA.y - pointB.y;
+
+ return Math.sqrt(d1 * d1 + d2 * d2);
+ }
+
+ //Compute the distance from AB to C
+ double LineToPointDistance2D(PointF pointA, PointF pointB, PointF pointC)
+ {
+ double dist = CrossProduct(pointA, pointB, pointC) / Distance(pointA, pointB);
+ if (true)
+ {
+ double dot1 = DotProduct(pointA, pointB, pointC);
+ if (dot1 > 0)
+ return Distance(pointB, pointC);
+
+ double dot2 = DotProduct(pointB, pointA, pointC);
+ if (dot2 > 0)
+ return Distance(pointA, pointC);
+ }
+ return Math.abs(dist);
+ }
+ }
+
}
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 346b887b..11ad2601 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
@@ -7,8 +7,9 @@ import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
-import android.view.View;
+import android.view.MotionEvent;
import android.widget.RelativeLayout;
+import android.widget.Toast;
import com.artifex.mupdf.fitz.Page;
import com.artifex.mupdf.fitz.R;
@@ -109,11 +110,6 @@ public class DocView extends DocViewBase implements DragHandleListener
mSelectionHandleBottomRight.show(show);
}
- private boolean getSelectionHandlesVisible()
- {
- return (mSelectionHandleTopLeft.getVisibility() == View.VISIBLE);
- }
-
@Override
protected void doSingleTap(float fx, float fy)
{
@@ -123,18 +119,20 @@ public class DocView extends DocViewBase implements DragHandleListener
if (dpv == null)
return;
- if (getSelectionHandlesVisible())
+ if (!getDrawMode())
{
- // hide handles and remove selection from pages
- showSelectionHandles(false);
- int numPages = getPageCount();
- for (int i = 0; i < numPages; i++)
+ if (dpv.onSingleTap(p.x, p.y))
{
- DocPageView cv = (DocPageView) getOrCreateChild(i);
- cv.removeSelection();
- if (cv.isReallyVisible())
- cv.invalidate();
+ onChangeSelection();
+ return;
}
+ onChangeSelection();
+ }
+
+ if (hasSelection())
+ {
+ clearSelection();
+ onChangeSelection();
}
else
{
@@ -142,20 +140,88 @@ public class DocView extends DocViewBase implements DragHandleListener
Rect r = dpv.selectWord(p);
if (r != null)
{
- // show handles
showSelectionHandles(true);
- // set highlight boundaries
selectionStartPage = dpv;
selectionStartLoc.set(r.left, r.top);
selectionEndPage = dpv;
selectionEndLoc.set(r.right, r.bottom);
moveHandlesToCorners();
+
+ onChangeSelection();
}
}
}
+ private void clearSelection()
+ {
+ selectionStartPage = null;
+ selectionEndPage = null;
+ showSelectionHandles(false);
+ int numPages = getPageCount();
+ for (int i = 0; i < numPages; i++)
+ {
+ DocPageView cv = (DocPageView) getOrCreateChild(i);
+ cv.removeSelection();
+ if (cv.isReallyVisible())
+ cv.invalidate();
+ }
+ }
+
+ public boolean hasSelection()
+ {
+ return (selectionStartPage != null && selectionEndPage != null);
+ }
+
+ public boolean hasInkAnnotationSelected()
+ {
+ int numPages = getPageCount();
+ for (int i = 0; i < numPages; i++)
+ {
+ DocPageView cv = (DocPageView) getOrCreateChild(i);
+ if (cv.hasInkAnnotationSelected())
+ return true;
+ }
+
+ return false;
+ }
+
+ public void setSelectedInkLineColor(int val)
+ {
+ int numPages = getPageCount();
+ for (int i = 0; i < numPages; i++)
+ {
+ DocPageView cv = (DocPageView) getOrCreateChild(i);
+ if (cv.hasInkAnnotationSelected())
+ cv.setSelectedInkLineColor(val);
+ }
+ }
+
+ public void setSelectedInkLineThickness(float val)
+ {
+ int numPages = getPageCount();
+ for (int i = 0; i < numPages; i++)
+ {
+ DocPageView cv = (DocPageView) getOrCreateChild(i);
+ if (cv.hasInkAnnotationSelected())
+ cv.setSelectedInkLineThickness(val);
+ }
+ }
+
+ private void onChangeSelection()
+ {
+ if (mSelectionChangeListener != null)
+ mSelectionChangeListener.onSelectionChanged();
+ }
+
+ private SelectionChangeListener mSelectionChangeListener = null;
+ public void setSelectionChangeListener (SelectionChangeListener l) {mSelectionChangeListener = l;}
+ public interface SelectionChangeListener
+ {
+ public void onSelectionChanged();
+ }
+
@Override
protected void doDoubleTap(float fx, float fy)
{
@@ -214,6 +280,7 @@ public class DocView extends DocViewBase implements DragHandleListener
selectionStartPage = pageView1;
p1 = pageView1.screenToPage(p1);
selectionStartLoc.set(p1.x, p1.y);
+ onChangeSelection();
}
}
@@ -228,6 +295,7 @@ public class DocView extends DocViewBase implements DragHandleListener
selectionEndPage = pageView2;
p2 = pageView2.screenToPage(p2);
selectionEndLoc.set(p2.x, p2.y);
+ onChangeSelection();
}
}
@@ -417,4 +485,129 @@ public class DocView extends DocViewBase implements DragHandleListener
}
}
+ public void onHighlight()
+ {
+ if (hasSelection())
+ {
+ Toast.makeText(getContext(),"onHighlight", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private boolean mNoteMode = false;
+ public boolean getNoteMode() {return mNoteMode;}
+ public void onNoteMode()
+ {
+ mNoteMode = !mNoteMode;
+ mDrawMode = false;
+ clearSelection();
+ onChangeSelection();
+ }
+
+ private boolean mDrawMode = false;
+ public boolean getDrawMode() {return mDrawMode;}
+ public void onDrawMode()
+ {
+ mDrawMode = !mDrawMode;
+ mNoteMode = false;
+ clearSelection();
+ onChangeSelection();
+ }
+
+ public void onDelete()
+ {
+ int numPages = getPageCount();
+ for (int i = 0; i < numPages; i++)
+ {
+ DocPageView cv = (DocPageView) getOrCreateChild(i);
+ if (cv.hasInkAnnotationSelected())
+ cv.deleteSelectedInkAnnotation();
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event)
+ {
+ if (mDrawMode)
+ {
+ float x = event.getX();
+ float y = event.getY();
+ switch (event.getAction())
+ {
+ case MotionEvent.ACTION_DOWN:
+ touch_start(x, y);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ touch_move(x, y);
+ break;
+ case MotionEvent.ACTION_UP:
+ touch_up();
+ break;
+ }
+
+ return true;
+ }
+
+ return super.onTouchEvent(event);
+ }
+
+ private float mX, mY;
+ private static final float TOUCH_TOLERANCE = 2;
+
+ private int mCurrentInkLineColor = 0xFFFF0000;
+ public void setInkLineColor(int val)
+ {
+ mCurrentInkLineColor=val;
+
+ // also change any selected annotation
+ if (hasInkAnnotationSelected())
+ setSelectedInkLineColor(val);
+ }
+ public int getInkLineColor() {return mCurrentInkLineColor;}
+
+ private float mCurrentInkLineThickness = 4.5f;
+ public float getInkLineThickness() {return mCurrentInkLineThickness;}
+ public void setInkLineThickness(float val)
+ {
+ mCurrentInkLineThickness=val;
+
+ // also change any selected annotation
+ if (hasInkAnnotationSelected())
+ setSelectedInkLineThickness(val);
+ }
+
+ private void touch_start(float x, float y)
+ {
+ Point p = eventToScreen(x, y);
+ final DocPageView dpv = findPageViewContainingPoint(p.x, p.y, false);
+ if (dpv != null)
+ {
+ dpv.startDraw(p.x, p.y, mCurrentInkLineColor, mCurrentInkLineThickness);
+ }
+
+ mX = x;
+ mY = y;
+ }
+
+ private void touch_move(float x, float y) {
+
+ float dx = Math.abs(x - mX);
+ float dy = Math.abs(y - mY);
+ if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE)
+ {
+ Point p = eventToScreen(x, y);
+ final DocPageView dpv = findPageViewContainingPoint(p.x, p.y, false);
+ if (dpv != null)
+ {
+ dpv.continueDraw(p.x, p.y);
+ }
+ mX = x;
+ mY = y;
+ }
+ }
+
+ private void touch_up()
+ {
+ // NOOP
+ }
+
}
diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/LineWidthDialog.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/LineWidthDialog.java
new file mode 100644
index 00000000..beea9d98
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/LineWidthDialog.java
@@ -0,0 +1,128 @@
+package com.artifex.mupdf.android;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import com.artifex.mupdf.fitz.R;
+
+import kankan.wheel.widget.OnWheelScrollListener;
+import kankan.wheel.widget.WheelView;
+import kankan.wheel.widget.adapters.WheelViewAdapter;
+
+public class LineWidthDialog
+{
+ private static final int POPUP_OFFSET = 30;
+
+ private static final float values[] = new float[] {0.25f, 0.5f, 1, 1.5f, 3, 4.5f, 6, 8, 12, 18, 24};
+
+ public static void show(Context context, View anchor, float val, final WidthChangedListener listener)
+ {
+ float currentValue = val;
+
+ View layout = View.inflate(context, R.layout.line_width_dialog, null);
+
+ View wv = layout.findViewById(R.id.wheel);
+ WheelView wheel = (WheelView)wv;
+
+ final LineWidthAdapter adapter = new LineWidthAdapter(context, values);
+
+ wheel.setViewAdapter(adapter);
+ wheel.setVisibleItems(5);
+
+ wheel.setCurrentItem(0);
+ for (int i=0;i<values.length;i++)
+ {
+ if (values[i] == currentValue)
+ wheel.setCurrentItem(i);
+ }
+
+ wheel.addScrollingListener(new OnWheelScrollListener() {
+ @Override
+ public void onScrollingStarted(WheelView wheel) {}
+
+ @Override
+ public void onScrollingFinished(WheelView wheel) {
+ listener.onWidthChanged(values[wheel.getCurrentItem()]);
+ }
+ });
+
+ // make a popup window
+ final PopupWindow popup = new PopupWindow(layout,
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ popup.setFocusable(true);
+
+ // now show the popup
+ popup.showAsDropDown(anchor,POPUP_OFFSET,POPUP_OFFSET);
+ }
+
+ public static class LineWidthAdapter implements WheelViewAdapter
+ {
+ public LineWidthAdapter(Context context, float values[]) {
+ super();
+ mValues = values;
+ mContext = context;
+ }
+
+ private Context mContext;
+ private float mValues[];
+
+ @Override
+ public View getItem(int position, View convertView, ViewGroup parent)
+ {
+ // make a View if needed.
+ if (convertView == null)
+ {
+ LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ convertView = inflater.inflate(R.layout.line_width_item, parent, false);
+ }
+
+ // get the value
+ float value = values[position];
+
+ // set the text string
+ TextView tv = ((TextView)convertView.findViewById(R.id.value));
+ if(value == (int) value)
+ tv.setText(String.format("%d pt",(int)value));
+ else
+ tv.setText(String.format("%s pt",value));
+
+ // adjust the height of the line
+ int h = (int)(value*3f/2f);
+ if (h<1)
+ h = 1;
+ View bar = ((View)convertView.findViewById(R.id.bar));
+ bar.getLayoutParams().height = h;
+ bar.setLayoutParams(bar.getLayoutParams());
+
+ return convertView;
+ }
+
+ @Override
+ public int getItemsCount() {
+ return mValues.length;
+ }
+
+ @Override
+ public View getEmptyItem(View convertView, ViewGroup parent) {
+ return null;
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver observer) {
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ }
+ }
+
+ public interface WidthChangedListener {
+ void onWidthChanged(float value);
+ }
+
+}
diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/ToolbarButton.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/ToolbarButton.java
new file mode 100644
index 00000000..5760df48
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/ToolbarButton.java
@@ -0,0 +1,31 @@
+package com.artifex.mupdf.android;
+
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.support.annotation.ColorInt;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+
+public class ToolbarButton extends ImageButton
+{
+ // Color.GRAY (0xFF888888) is too dark. Use something lighter.
+ @ColorInt
+ private static final int MYGRAY = 0xFFAAAAAA;
+
+ public ToolbarButton(Context context) {
+ super(context);
+ }
+
+ public ToolbarButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override public void setEnabled(boolean enabled)
+ {
+ super.setEnabled(enabled);
+ if (enabled)
+ setColorFilter(null);
+ else
+ setColorFilter(MYGRAY, PorterDuff.Mode.SRC_IN);
+ }
+}
diff --git a/platform/android/example/mupdf/src/main/res/drawable/colors.xml b/platform/android/example/mupdf/src/main/res/drawable/colors.xml
new file mode 100644
index 00000000..eff1250e
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/drawable/colors.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <stroke android:width="1dp" android:color="#797979" />
+ <solid android:color="@color/bluish_grey" />
+ <corners
+ android:radius="7dp" />
+ <padding
+ android:top="10dp"
+ android:left="10dp"
+ android:right="10dp"
+ android:bottom="10dp"
+ />
+</shape>
diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_delete.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_delete.xml
new file mode 100755
index 00000000..d027b030
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/drawable/icon_delete.xml
@@ -0,0 +1,20 @@
+<?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.331"
+ android:pathData="M 25 9.92 C 33.3284540274 9.92 40.08 16.6715459726 40.08 25 C 40.08 33.3284540274 33.3284540274 40.08 25 40.08 C 16.6715459726 40.08 9.92 33.3284540274 9.92 25 C 9.92 16.6715459726 16.6715459726 9.92 25 9.92 Z" />
+ <path
+ android:strokeColor="#333333"
+ android:strokeWidth="1.774"
+ android:pathData="M 17.622 32.934 L 32.257 18.299" />
+ <path
+ android:strokeColor="#333333"
+ android:strokeWidth="1.774"
+ android:pathData="M 32.257 32.934 L 17.622 18.297" />
+</vector> \ No newline at end of file
diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_draw.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_draw.xml
new file mode 100644
index 00000000..808efdaa
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/drawable/icon_draw.xml
@@ -0,0 +1,26 @@
+<?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="M33.1075,16.6386 L38.7232,19.2633 L31.7612,34.159 L26.1455,31.5343
+L33.1075,16.6386 Z" />
+ <path
+ android:fillColor="#333333"
+ android:pathData="M25.735,32.435 L31.334,35.052 L25.884,39.634 Z" />
+ <path
+ android:fillColor="#333333"
+ android:pathData="M39.29,18.067 L41.299,13.773 C41.358,13.646,41.302,13.495,41.176,13.435
+L36.02,11.025 C35.893,10.965,35.741,11.021,35.682,11.148 L33.675,15.442
+L39.29,18.067 Z" />
+ <path
+ android:strokeColor="#333333"
+ android:strokeWidth="1.04"
+ android:strokeLineCap="round"
+ android:pathData="M8.738,10.366 C8.738,10.366,26.245,13.833,24.685,17.3
+C23.125,20.765,9.953,21.286,8.738,25.793 C7.525,30.3,24.859,39.313,24.859,39.313" />
+</vector> \ No newline at end of file
diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_highlight.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_highlight.xml
new file mode 100755
index 00000000..f42b88d9
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/drawable/icon_highlight.xml
@@ -0,0 +1,21 @@
+<?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="M 20.629 22.61 L 27.374 29.355 L 22.483 30.848 L 19.701 28.068 Z" />
+ <path
+ android:fillColor="#333333"
+ android:pathData="M27.909,28.668l12.17-12.032c0.153-0.154,0.153-0.404,0.001-0.557l-6.199-6.251
+c-0.154-0.154-0.404-0.155-0.559-0.001L21.154,21.861L27.909,28.668z" />
+ <path
+ android:fillColor="#333333"
+ android:pathData="M 18.992 28.629 L 14.841 32.359 L 20.726 32.404 L 21.803 31.594 Z" />
+ <path
+ android:fillColor="#333333"
+ android:pathData="M 9.76 34.59 H 40.24 V 40.287 H 9.76 V 34.59 Z" />
+</vector> \ No newline at end of file
diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_line_color.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_line_color.xml
new file mode 100755
index 00000000..1c79066a
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/drawable/icon_line_color.xml
@@ -0,0 +1,14 @@
+<?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="#81DBE7"
+ android:strokeWidth="1.56"
+ android:pathData="M40.797,35.303
+c0,1.518-1.229,2.747-2.747,2.747h-26.1c-1.516,0-2.747-1.229-2.747-2.747V14.698c0-1.518,1.231-2.748,2.747-2.748h26.1
+c1.519,0,2.747,1.23,2.747,2.748V35.303z" />
+</vector> \ No newline at end of file
diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_line_thickness.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_line_thickness.xml
new file mode 100755
index 00000000..1e48a6c9
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/drawable/icon_line_thickness.xml
@@ -0,0 +1,14 @@
+<?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.6"
+ android:pathData="M40.797,35.303
+c0,1.518-1.229,2.747-2.747,2.747h-26.1c-1.517,0-2.747-1.229-2.747-2.747V14.698c0-1.518,1.23-2.748,2.747-2.748h26.1
+c1.519,0,2.747,1.23,2.747,2.748V35.303z" />
+</vector> \ No newline at end of file
diff --git a/platform/android/example/mupdf/src/main/res/drawable/icon_note.xml b/platform/android/example/mupdf/src/main/res/drawable/icon_note.xml
new file mode 100755
index 00000000..1f5732e3
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/drawable/icon_note.xml
@@ -0,0 +1,30 @@
+<?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.372"
+ android:strokeMiterLimit="10"
+ android:pathData="M12.204,32.945
+H10.09c-0.632,0-1.145-0.516-1.145-1.145V11.896c0-0.63,0.513-1.143,1.145-1.143h29.821c0.632,0,1.144,0.513,1.144,1.143v19.904
+c0,0.629-0.512,1.145-1.144,1.145H18.507l-6.303,6.301V32.945z" />
+ <path
+ android:strokeColor="#333333"
+ android:strokeWidth="1.769"
+ android:strokeMiterLimit="10"
+ android:pathData="M 16.993 17.862 L 30.559 17.862" />
+ <path
+ android:strokeColor="#333333"
+ android:strokeWidth="1.769"
+ android:strokeMiterLimit="10"
+ android:pathData="M 16.993 26.564 L 28.381 26.564" />
+ <path
+ android:strokeColor="#333333"
+ android:strokeWidth="1.769"
+ android:strokeMiterLimit="10"
+ android:pathData="M 16.993 22.213 L 31.885 22.213" />
+</vector> \ No newline at end of file
diff --git a/platform/android/example/mupdf/src/main/res/drawable/number_format_popup.xml b/platform/android/example/mupdf/src/main/res/drawable/number_format_popup.xml
new file mode 100644
index 00000000..5154168b
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/drawable/number_format_popup.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <stroke android:width="1dp" android:color="@color/formula_popup_background" />
+ <solid android:color="@color/number_format_popup_background" />
+ <corners
+ android:radius="7dp" />
+ <padding
+ android:top="5dp"
+ android:left="5dp"
+ android:right="5dp"
+ android:bottom="5dp"
+ />
+</shape>
diff --git a/platform/android/example/mupdf/src/main/res/drawable/toolbar_button2.xml b/platform/android/example/mupdf/src/main/res/drawable/toolbar_button2.xml
new file mode 100644
index 00000000..d624e85f
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/drawable/toolbar_button2.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true">
+ <shape>
+ <solid android:color="@color/white" />
+ <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/transparent_color_swatch.xml b/platform/android/example/mupdf/src/main/res/drawable/transparent_color_swatch.xml
new file mode 100644
index 00000000..fdf3d369
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/drawable/transparent_color_swatch.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item>
+ <shape>
+ <stroke android:color="@color/black" android:width="1dp" />
+ <solid android:color="@color/white" />
+ </shape>
+ </item>
+
+ <item>
+ <rotate
+ android:fromDegrees="45">
+ <shape
+ android:shape="line" >
+ <stroke android:color="@color/red" android:width="4dp" />
+ </shape>
+ </rotate>
+ </item>
+
+</layer-list>
diff --git a/platform/android/example/mupdf/src/main/res/drawable/wheel_bg.xml b/platform/android/example/mupdf/src/main/res/drawable/wheel_bg.xml
new file mode 100644
index 00000000..f9fdd27a
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/drawable/wheel_bg.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Android Wheel Control.
+ http://android-devblog.blogspot.com/2010/05/wheel-ui-contol.html
+
+ Copyright 2010 Yuri Kanivets
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="#ffffff" />
+ <stroke android:width="1dp" android:color="#ffffff" />
+ </shape>
+ </item>
+
+</layer-list> \ No newline at end of file
diff --git a/platform/android/example/mupdf/src/main/res/drawable/wheel_val.xml b/platform/android/example/mupdf/src/main/res/drawable/wheel_val.xml
new file mode 100644
index 00000000..45f7f5c9
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/drawable/wheel_val.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Android Wheel Control.
+ http://android-devblog.blogspot.com/2010/05/wheel-ui-contol.html
+
+ Copyright 2010 Yuri Kanivets
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="#00ffffff"/>
+ <stroke android:width="1dp"
+ android:dashWidth="3px"
+ android:dashGap="3px"
+ android:color="#ff000000" />
+</shape>
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
index 767a27b0..50814a44 100644
--- a/platform/android/example/mupdf/src/main/res/layout/annotate_toolbar.xml
+++ b/platform/android/example/mupdf/src/main/res/layout/annotate_toolbar.xml
@@ -19,27 +19,168 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
+ android:gravity="center_horizontal"
android:paddingLeft="10dp"
android:paddingRight="10dp">
- <ImageButton
+ <com.artifex.mupdf.android.ToolbarButton
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/show_annot_button"
- android:src="@drawable/icon_toggle_annotations" />
+ android:id="@+id/highlight_button"
+ android:src="@drawable/icon_highlight" />
<TextView
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textColor="@color/black"
+ android:textSize="11sp"
+ android:text="HIGHLIGHT"/>
+ </LinearLayout>
+
+ <!--a divider-->
+ <View
+ android:layout_width="1dp"
+ android:paddingRight="3dp" android:paddingLeft="3dp"
+ android:layout_height="match_parent"
+ android:background="#FF8E8F90" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:id="@+id/draw_tools_holder"
+ android:background="@drawable/toolbar_button2"
+ >
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center_horizontal"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp">
+
+ <com.artifex.mupdf.android.ToolbarButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scaleType="fitXY"
+ android:layout_centerVertical="true"
+ android:background="@drawable/toolbar_button2"
+ android:id="@+id/draw_button"
+ android:src="@drawable/icon_draw" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textColor="@color/black"
+ android:textSize="11sp"
+ android:text="DRAW"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center_horizontal"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp">
+
+ <com.artifex.mupdf.android.ToolbarButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scaleType="fitXY"
+ android:layout_centerVertical="true"
+ android:background="@drawable/toolbar_button2"
+ android:id="@+id/line_color_button"
+ android:src="@drawable/icon_line_color" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textColor="@color/black"
+ android:textSize="11sp"
+ android:text=""/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center_horizontal"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp">
+
+ <com.artifex.mupdf.android.ToolbarButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scaleType="fitXY"
+ android:layout_centerVertical="true"
+ android:background="@drawable/toolbar_button2"
+ android:id="@+id/line_thickness_button"
+ android:src="@drawable/icon_line_thickness" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textColor="@color/black"
+ android:textSize="11sp"
+ android:text=""/>
+ </LinearLayout>
+
+
+ </LinearLayout>
+
+ <!--a divider-->
+ <View
+ android:layout_width="1dp"
+ android:paddingRight="3dp" android:paddingLeft="3dp"
+ android:layout_height="match_parent"
+ android:background="#FF8E8F90" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:id="@+id/note_holder"
+ android:background="@drawable/toolbar_button2"
+ >
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center_horizontal"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp">
+
+ <com.artifex.mupdf.android.ToolbarButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scaleType="fitXY"
+ android:layout_centerVertical="true"
+ android:background="@drawable/toolbar_button2"
+ android:id="@+id/note_button"
+ android:src="@drawable/icon_note" />
+
+ <TextView
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/black"
android:textSize="11sp"
- android:text="TOGGLE"/>
+ android:text="NOTE"/>
</LinearLayout>
+
+ </LinearLayout>
+
+
<!--a divider-->
<View
android:layout_width="1dp"
@@ -47,6 +188,78 @@
android:layout_height="match_parent"
android:background="#FF8E8F90" />
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center_horizontal"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp">
+
+ <com.artifex.mupdf.android.ToolbarButton
+ 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/delete_button"
+ android:src="@drawable/icon_delete" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textColor="@color/black"
+ android:textSize="11sp"
+ android:text="DELETE"/>
+ </LinearLayout>
+
+
+ <!--
+ this layout holds some buttons for testing
+ normally it's not shown.
+ -->
+
+ <LinearLayout
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <!--a divider-->
+ <View
+ android:layout_width="1dp"
+ android:paddingRight="3dp" android:paddingLeft="3dp"
+ android:layout_height="match_parent"
+ android:background="#FF8E8F90" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center_horizontal"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp">
+
+ <com.artifex.mupdf.android.ToolbarButton
+ 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/show_annot_button"
+ android:src="@drawable/icon_toggle_annotations" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textColor="@color/black"
+ android:textSize="11sp"
+ android:text="TOGGLE"/>
+ </LinearLayout>
+
+ </LinearLayout>
+
</LinearLayout>
</HorizontalScrollView>
diff --git a/platform/android/example/mupdf/src/main/res/layout/colors.xml b/platform/android/example/mupdf/src/main/res/layout/colors.xml
new file mode 100644
index 00000000..d7d7aa91
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/layout/colors.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/colors"
+ android:orientation="vertical"
+ >
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="false"
+ android:layout_alignParentTop="true"
+ android:id="@+id/font_panel">
+
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:background="@color/bluish_grey"
+ android:textColor="#FFFFFFFF"
+ android:textSize="12pt"
+ android:id="@+id/color_dialog_title" />
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:id="@+id/fontcolors_row1" >
+
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+
+ </LinearLayout>
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:id="@+id/fontcolors_row2">
+
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+
+ </LinearLayout>
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:id="@+id/fontcolors_row3">
+
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+ <Button style="@style/font_color_button" android:background="#000000" android:tag="#000000"/>
+
+ <Button style="@style/font_color_button"
+ android:id="@+id/transparent_color_button"
+ android:background="@drawable/transparent_color_swatch"
+ android:tag="transparent"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </RelativeLayout>
+
+ <!--<Button-->
+ <!--android:layout_width="wrap_content"-->
+ <!--android:layout_height="wrap_content"-->
+ <!--android:text="@string/done"-->
+ <!--android:id="@+id/done_button"-->
+ <!--android:background="@drawable/button"-->
+ <!--android:textColor="#FFFFFFFF"-->
+ <!--android:layout_gravity="right"/>-->
+
+</LinearLayout>
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
index 6468c9d0..a35d2ed9 100644
--- a/platform/android/example/mupdf/src/main/res/layout/file_toolbar.xml
+++ b/platform/android/example/mupdf/src/main/res/layout/file_toolbar.xml
@@ -22,7 +22,7 @@
android:paddingLeft="10dp"
android:paddingRight="10dp">
- <ImageButton
+ <com.artifex.mupdf.android.ToolbarButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY"
@@ -47,7 +47,7 @@
android:paddingLeft="10dp"
android:paddingRight="10dp">
- <ImageButton
+ <com.artifex.mupdf.android.ToolbarButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY"
@@ -79,7 +79,7 @@
android:paddingLeft="10dp"
android:paddingRight="10dp">
- <ImageButton
+ <com.artifex.mupdf.android.ToolbarButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY"
@@ -104,7 +104,7 @@
android:paddingLeft="10dp"
android:paddingRight="10dp">
- <ImageButton
+ <com.artifex.mupdf.android.ToolbarButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY"
@@ -129,7 +129,7 @@
android:paddingLeft="10dp"
android:paddingRight="10dp">
- <ImageButton
+ <com.artifex.mupdf.android.ToolbarButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY"
diff --git a/platform/android/example/mupdf/src/main/res/layout/line_width_dialog.xml b/platform/android/example/mupdf/src/main/res/layout/line_width_dialog.xml
new file mode 100644
index 00000000..5c47ab1d
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/layout/line_width_dialog.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:background="@drawable/number_format_popup">
+
+ <kankan.wheel.widget.WheelView
+ android:layout_width="300dp"
+ android:layout_height="wrap_content"
+ android:id="@+id/wheel">
+
+ </kankan.wheel.widget.WheelView>
+
+</LinearLayout>
diff --git a/platform/android/example/mupdf/src/main/res/layout/line_width_item.xml b/platform/android/example/mupdf/src/main/res/layout/line_width_item.xml
new file mode 100644
index 00000000..0d960c7e
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/layout/line_width_item.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <View
+ android:id="@+id/bar"
+ android:layout_gravity="center_vertical"
+ android:layout_width="200dp"
+ android:layout_marginLeft="5dp"
+ android:layout_marginRight="5dp"
+ android:layout_height="1dp"
+ android:background="@color/black" />
+
+ <TextView
+ android:id="@+id/value"
+ android:layout_gravity="center_vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="10dp"
+ android:textColor="@color/black"
+ android:textSize="20sp"/>
+
+</LinearLayout>
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
index 06ffa233..22e01a20 100644
--- a/platform/android/example/mupdf/src/main/res/layout/pages_toolbar.xml
+++ b/platform/android/example/mupdf/src/main/res/layout/pages_toolbar.xml
@@ -23,7 +23,7 @@
android:paddingLeft="10dp"
android:paddingRight="10dp">
- <ImageButton
+ <com.artifex.mupdf.android.ToolbarButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY"
@@ -50,7 +50,7 @@
android:paddingRight="10dp"
>
- <ImageButton
+ <com.artifex.mupdf.android.ToolbarButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY"
@@ -85,7 +85,7 @@
android:paddingRight="10dp"
>
- <ImageButton
+ <com.artifex.mupdf.android.ToolbarButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY"
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 973639bc..2b67e67d 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
@@ -63,7 +63,7 @@
android:gravity="center"
>
- <ImageButton
+ <com.artifex.mupdf.android.ToolbarButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY"
@@ -88,7 +88,7 @@
android:gravity="center"
>
- <ImageButton
+ <com.artifex.mupdf.android.ToolbarButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY"
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 3709da70..cc919a69 100644
--- a/platform/android/example/mupdf/src/main/res/values/strings.xml
+++ b/platform/android/example/mupdf/src/main/res/values/strings.xml
@@ -13,4 +13,7 @@
<string name="ok">OK</string>
<string name="cancel">Cancel</string>
+ <string name="background">Background</string>
+ <string name="color">Color</string>
+
</resources>
diff --git a/platform/android/example/mupdf/src/main/res/values/styles.xml b/platform/android/example/mupdf/src/main/res/values/styles.xml
new file mode 100644
index 00000000..d767ff7f
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <!-- font color selection button. -->
+ <style name="font_color_button" parent="@android:style/Widget.Button">
+ <item name="android:layout_height">20pt</item>
+ <item name="android:layout_width">20pt</item>
+ <item name="android:layout_margin">12dp</item>
+ </style>
+
+</resources>
diff --git a/platform/java/src/kankan/wheel/widget/ItemsRange.java b/platform/java/src/kankan/wheel/widget/ItemsRange.java
new file mode 100644
index 00000000..8b58a557
--- /dev/null
+++ b/platform/java/src/kankan/wheel/widget/ItemsRange.java
@@ -0,0 +1,81 @@
+/*
+ * Android Wheel Control.
+ * https://code.google.com/p/android-wheel/
+ *
+ * Copyright 2011 Yuri Kanivets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kankan.wheel.widget;
+
+/**
+ * Range for visible items.
+ */
+public class ItemsRange {
+ // First item number
+ private int first;
+
+ // Items count
+ private int count;
+
+ /**
+ * Default constructor. Creates an empty range
+ */
+ public ItemsRange() {
+ this(0, 0);
+ }
+
+ /**
+ * Constructor
+ * @param first the number of first item
+ * @param count the count of items
+ */
+ public ItemsRange(int first, int count) {
+ this.first = first;
+ this.count = count;
+ }
+
+ /**
+ * Gets number of first item
+ * @return the number of the first item
+ */
+ public int getFirst() {
+ return first;
+ }
+
+ /**
+ * Gets number of last item
+ * @return the number of last item
+ */
+ public int getLast() {
+ return getFirst() + getCount() - 1;
+ }
+
+ /**
+ * Get items count
+ * @return the count of items
+ */
+ public int getCount() {
+ return count;
+ }
+
+ /**
+ * Tests whether item is contained by range
+ * @param index the item number
+ * @return true if item is contained
+ */
+ public boolean contains(int index) {
+ return index >= getFirst() && index <= getLast();
+ }
+}
diff --git a/platform/java/src/kankan/wheel/widget/OnWheelChangedListener.java b/platform/java/src/kankan/wheel/widget/OnWheelChangedListener.java
new file mode 100644
index 00000000..baad897c
--- /dev/null
+++ b/platform/java/src/kankan/wheel/widget/OnWheelChangedListener.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 Yuri Kanivets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kankan.wheel.widget;
+
+/**
+ * Wheel changed listener interface.
+ * <p>The onChanged() method is called whenever current wheel positions is changed:
+ * <li> New Wheel position is set
+ * <li> Wheel view is scrolled
+ */
+public interface OnWheelChangedListener {
+ /**
+ * Callback method to be invoked when current item changed
+ * @param wheel the wheel view whose state has changed
+ * @param oldValue the old value of current item
+ * @param newValue the new value of current item
+ */
+ void onChanged(WheelView wheel, int oldValue, int newValue);
+}
diff --git a/platform/java/src/kankan/wheel/widget/OnWheelClickedListener.java b/platform/java/src/kankan/wheel/widget/OnWheelClickedListener.java
new file mode 100644
index 00000000..040bfc7b
--- /dev/null
+++ b/platform/java/src/kankan/wheel/widget/OnWheelClickedListener.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 Yuri Kanivets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kankan.wheel.widget;
+
+/**
+ * Wheel clicked listener interface.
+ * <p>The onItemClicked() method is called whenever a wheel item is clicked
+ * <li> New Wheel position is set
+ * <li> Wheel view is scrolled
+ */
+public interface OnWheelClickedListener {
+ /**
+ * Callback method to be invoked when current item clicked
+ * @param wheel the wheel view
+ * @param itemIndex the index of clicked item
+ */
+ void onItemClicked(WheelView wheel, int itemIndex);
+}
diff --git a/platform/java/src/kankan/wheel/widget/OnWheelScrollListener.java b/platform/java/src/kankan/wheel/widget/OnWheelScrollListener.java
new file mode 100644
index 00000000..8571a4fe
--- /dev/null
+++ b/platform/java/src/kankan/wheel/widget/OnWheelScrollListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010 Yuri Kanivets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kankan.wheel.widget;
+
+/**
+ * Wheel scrolled listener interface.
+ */
+public interface OnWheelScrollListener {
+ /**
+ * Callback method to be invoked when scrolling started.
+ * @param wheel the wheel view whose state has changed.
+ */
+ void onScrollingStarted(WheelView wheel);
+
+ /**
+ * Callback method to be invoked when scrolling ended.
+ * @param wheel the wheel view whose state has changed.
+ */
+ void onScrollingFinished(WheelView wheel);
+}
diff --git a/platform/java/src/kankan/wheel/widget/WheelAdapter.java b/platform/java/src/kankan/wheel/widget/WheelAdapter.java
new file mode 100644
index 00000000..841c0b15
--- /dev/null
+++ b/platform/java/src/kankan/wheel/widget/WheelAdapter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010 Yuri Kanivets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kankan.wheel.widget;
+
+/**
+ * Wheel adapter interface
+ *
+ * @deprecated Use WheelViewAdapter
+ */
+public interface WheelAdapter {
+ /**
+ * Gets items count
+ * @return the count of wheel items
+ */
+ public int getItemsCount();
+
+ /**
+ * Gets a wheel item by index.
+ *
+ * @param index the item index
+ * @return the wheel item text or null
+ */
+ public String getItem(int index);
+
+ /**
+ * Gets maximum item length. It is used to determine the wheel width.
+ * If -1 is returned there will be used the default wheel width.
+ *
+ * @return the maximum item length or -1
+ */
+ public int getMaximumLength();
+}
diff --git a/platform/java/src/kankan/wheel/widget/WheelRecycle.java b/platform/java/src/kankan/wheel/widget/WheelRecycle.java
new file mode 100644
index 00000000..242fc89e
--- /dev/null
+++ b/platform/java/src/kankan/wheel/widget/WheelRecycle.java
@@ -0,0 +1,153 @@
+/*
+ * Android Wheel Control.
+ * https://code.google.com/p/android-wheel/
+ *
+ * Copyright 2011 Yuri Kanivets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kankan.wheel.widget;
+
+import android.view.View;
+import android.widget.LinearLayout;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Recycle stores wheel items to reuse.
+ */
+public class WheelRecycle {
+ // Cached items
+ private List<View> items;
+
+ // Cached empty items
+ private List<View> emptyItems;
+
+ // Wheel view
+ private WheelView wheel;
+
+ /**
+ * Constructor
+ * @param wheel the wheel view
+ */
+ public WheelRecycle(WheelView wheel) {
+ this.wheel = wheel;
+ }
+
+ /**
+ * Recycles items from specified layout.
+ * There are saved only items not included to specified range.
+ * All the cached items are removed from original layout.
+ *
+ * @param layout the layout containing items to be cached
+ * @param firstItem the number of first item in layout
+ * @param range the range of current wheel items
+ * @return the new value of first item number
+ */
+ public int recycleItems(LinearLayout layout, int firstItem, ItemsRange range) {
+ int index = firstItem;
+ for (int i = 0; i < layout.getChildCount();) {
+ if (!range.contains(index)) {
+ recycleView(layout.getChildAt(i), index);
+ layout.removeViewAt(i);
+ if (i == 0) { // first item
+ firstItem++;
+ }
+ } else {
+ i++; // go to next item
+ }
+ index++;
+ }
+ return firstItem;
+ }
+
+ /**
+ * Gets item view
+ * @return the cached view
+ */
+ public View getItem() {
+ return getCachedView(items);
+ }
+
+ /**
+ * Gets empty item view
+ * @return the cached empty view
+ */
+ public View getEmptyItem() {
+ return getCachedView(emptyItems);
+ }
+
+ /**
+ * Clears all views
+ */
+ public void clearAll() {
+ if (items != null) {
+ items.clear();
+ }
+ if (emptyItems != null) {
+ emptyItems.clear();
+ }
+ }
+
+ /**
+ * Adds view to specified cache. Creates a cache list if it is null.
+ * @param view the view to be cached
+ * @param cache the cache list
+ * @return the cache list
+ */
+ private List<View> addView(View view, List<View> cache) {
+ if (cache == null) {
+ cache = new LinkedList<View>();
+ }
+
+ cache.add(view);
+ return cache;
+ }
+
+ /**
+ * Adds view to cache. Determines view type (item view or empty one) by index.
+ * @param view the view to be cached
+ * @param index the index of view
+ */
+ private void recycleView(View view, int index) {
+ int count = wheel.getViewAdapter().getItemsCount();
+
+ if ((index < 0 || index >= count) && !wheel.isCyclic()) {
+ // empty view
+ emptyItems = addView(view, emptyItems);
+ } else {
+ while (index < 0) {
+ index = count + index;
+ }
+ index %= count;
+ items = addView(view, items);
+ }
+ }
+
+ /**
+ * Gets view from specified cache.
+ * @param cache the cache
+ * @return the first view from cache.
+ */
+ private View getCachedView(List<View> cache) {
+ if (cache != null && cache.size() > 0) {
+ View view = cache.get(0);
+ cache.remove(0);
+ return view;
+ }
+ return null;
+ }
+
+}
diff --git a/platform/java/src/kankan/wheel/widget/WheelScroller.java b/platform/java/src/kankan/wheel/widget/WheelScroller.java
new file mode 100644
index 00000000..0372da1a
--- /dev/null
+++ b/platform/java/src/kankan/wheel/widget/WheelScroller.java
@@ -0,0 +1,252 @@
+/*
+ * Android Wheel Control.
+ * https://code.google.com/p/android-wheel/
+ *
+ * Copyright 2011 Yuri Kanivets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kankan.wheel.widget;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.animation.Interpolator;
+import android.widget.Scroller;
+
+/**
+ * Scroller class handles scrolling events and updates the
+ */
+public class WheelScroller {
+ /**
+ * Scrolling listener interface
+ */
+ public interface ScrollingListener {
+ /**
+ * Scrolling callback called when scrolling is performed.
+ * @param distance the distance to scroll
+ */
+ void onScroll(int distance);
+
+ /**
+ * Starting callback called when scrolling is started
+ */
+ void onStarted();
+
+ /**
+ * Finishing callback called after justifying
+ */
+ void onFinished();
+
+ /**
+ * Justifying callback called to justify a view when scrolling is ended
+ */
+ void onJustify();
+ }
+
+ /** Scrolling duration */
+ private static final int SCROLLING_DURATION = 400;
+
+ /** Minimum delta for scrolling */
+ public static final int MIN_DELTA_FOR_SCROLLING = 1;
+
+ // Listener
+ private ScrollingListener listener;
+
+ // Context
+ private Context context;
+
+ // Scrolling
+ private GestureDetector gestureDetector;
+ private Scroller scroller;
+ private int lastScrollY;
+ private float lastTouchedY;
+ private boolean isScrollingPerformed;
+
+ /**
+ * Constructor
+ * @param context the current context
+ * @param listener the scrolling listener
+ */
+ public WheelScroller(Context context, ScrollingListener listener) {
+ gestureDetector = new GestureDetector(context, gestureListener);
+ gestureDetector.setIsLongpressEnabled(false);
+
+ scroller = new Scroller(context);
+
+ this.listener = listener;
+ this.context = context;
+ }
+
+ /**
+ * Set the the specified scrolling interpolator
+ * @param interpolator the interpolator
+ */
+ public void setInterpolator(Interpolator interpolator) {
+ scroller.forceFinished(true);
+ scroller = new Scroller(context, interpolator);
+ }
+
+ /**
+ * Scroll the wheel
+ * @param distance the scrolling distance
+ * @param time the scrolling duration
+ */
+ public void scroll(int distance, int time) {
+ scroller.forceFinished(true);
+
+ lastScrollY = 0;
+
+ scroller.startScroll(0, 0, 0, distance, time != 0 ? time : SCROLLING_DURATION);
+ setNextMessage(MESSAGE_SCROLL);
+
+ startScrolling();
+ }
+
+ /**
+ * Stops scrolling
+ */
+ public void stopScrolling() {
+ scroller.forceFinished(true);
+ }
+
+ /**
+ * Handles Touch event
+ * @param event the motion event
+ * @return
+ */
+ public boolean onTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ lastTouchedY = event.getY();
+ scroller.forceFinished(true);
+ clearMessages();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ // perform scrolling
+ int distanceY = (int)(event.getY() - lastTouchedY);
+ if (distanceY != 0) {
+ startScrolling();
+ listener.onScroll(distanceY);
+ lastTouchedY = event.getY();
+ }
+ break;
+ }
+
+ if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) {
+ justify();
+ }
+
+ return true;
+ }
+
+ // gesture listener
+ private SimpleOnGestureListener gestureListener = new SimpleOnGestureListener() {
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ // Do scrolling in onTouchEvent() since onScroll() are not call immediately
+ // when user touch and move the wheel
+ return true;
+ }
+
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ lastScrollY = 0;
+ final int maxY = 0x7FFFFFFF;
+ final int minY = -maxY;
+ scroller.fling(0, lastScrollY, 0, (int) -velocityY, 0, 0, minY, maxY);
+ setNextMessage(MESSAGE_SCROLL);
+ return true;
+ }
+ };
+
+ // Messages
+ private final int MESSAGE_SCROLL = 0;
+ private final int MESSAGE_JUSTIFY = 1;
+
+ /**
+ * Set next message to queue. Clears queue before.
+ *
+ * @param message the message to set
+ */
+ private void setNextMessage(int message) {
+ clearMessages();
+ animationHandler.sendEmptyMessage(message);
+ }
+
+ /**
+ * Clears messages from queue
+ */
+ private void clearMessages() {
+ animationHandler.removeMessages(MESSAGE_SCROLL);
+ animationHandler.removeMessages(MESSAGE_JUSTIFY);
+ }
+
+ // animation handler
+ private Handler animationHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ scroller.computeScrollOffset();
+ int currY = scroller.getCurrY();
+ int delta = lastScrollY - currY;
+ lastScrollY = currY;
+ if (delta != 0) {
+ listener.onScroll(delta);
+ }
+
+ // scrolling is not finished when it comes to final Y
+ // so, finish it manually
+ if (Math.abs(currY - scroller.getFinalY()) < MIN_DELTA_FOR_SCROLLING) {
+ currY = scroller.getFinalY();
+ scroller.forceFinished(true);
+ }
+ if (!scroller.isFinished()) {
+ animationHandler.sendEmptyMessage(msg.what);
+ } else if (msg.what == MESSAGE_SCROLL) {
+ justify();
+ } else {
+ finishScrolling();
+ }
+ }
+ };
+
+ /**
+ * Justifies wheel
+ */
+ private void justify() {
+ listener.onJustify();
+ setNextMessage(MESSAGE_JUSTIFY);
+ }
+
+ /**
+ * Starts scrolling
+ */
+ private void startScrolling() {
+ if (!isScrollingPerformed) {
+ isScrollingPerformed = true;
+ listener.onStarted();
+ }
+ }
+
+ /**
+ * Finishes scrolling
+ */
+ void finishScrolling() {
+ if (isScrollingPerformed) {
+ listener.onFinished();
+ isScrollingPerformed = false;
+ }
+ }
+}
diff --git a/platform/java/src/kankan/wheel/widget/WheelView.java b/platform/java/src/kankan/wheel/widget/WheelView.java
new file mode 100644
index 00000000..e8324e88
--- /dev/null
+++ b/platform/java/src/kankan/wheel/widget/WheelView.java
@@ -0,0 +1,889 @@
+/*
+ * Android Wheel Control.
+ * https://code.google.com/p/android-wheel/
+ *
+ * Copyright 2011 Yuri Kanivets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kankan.wheel.widget;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.GradientDrawable.Orientation;
+import android.media.AudioManager;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.view.animation.Interpolator;
+import android.widget.LinearLayout;
+
+import com.artifex.mupdf.fitz.R;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import kankan.wheel.widget.adapters.WheelViewAdapter;
+
+
+
+/**
+ * Numeric wheel view.
+ *
+ * @author Yuri Kanivets
+ */
+public class WheelView extends View {
+
+ /** Top and bottom shadows colors */
+ private static final int[] SHADOWS_COLORS = new int[] { 0xFF111111,
+ 0x00AAAAAA, 0x00AAAAAA };
+
+ /** Top and bottom items offset (to hide that) */
+ private static final int ITEM_OFFSET_PERCENT = 10;
+
+ /** Left and right padding value */
+ private static final int PADDING = 10;
+
+ /** Default count of visible items */
+ private static final int DEF_VISIBLE_ITEMS = 5;
+
+ // Wheel Values
+ private int currentItem = 0;
+
+ // Count of visible items
+ private int visibleItems = DEF_VISIBLE_ITEMS;
+
+ // Item height
+ private int itemHeight = 0;
+
+ // Center Line
+ private Drawable centerDrawable;
+
+ // // Shadows drawables
+ private GradientDrawable topShadow;
+ private GradientDrawable bottomShadow;
+
+ // Scrolling
+ private WheelScroller scroller;
+ private boolean isScrollingPerformed;
+ private int scrollingOffset;
+
+ // Cyclic
+ boolean isCyclic = false;
+
+ // Items layout
+ private LinearLayout itemsLayout;
+
+ // The number of first item in layout
+ private int firstItem;
+
+ // View adapter
+ private WheelViewAdapter viewAdapter;
+
+ // Recycle
+ private WheelRecycle recycle = new WheelRecycle(this);
+
+ // Listeners
+ private List<OnWheelChangedListener> changingListeners = new LinkedList<OnWheelChangedListener>();
+ private List<OnWheelScrollListener> scrollingListeners = new LinkedList<OnWheelScrollListener>();
+ private List<OnWheelClickedListener> clickingListeners = new LinkedList<OnWheelClickedListener>();
+
+ /**
+ * Constructor
+ */
+ public WheelView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ initData(context);
+ }
+
+ /**
+ * Constructor
+ */
+ public WheelView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initData(context);
+ }
+
+ /**
+ * Constructor
+ */
+ public WheelView(Context context) {
+ super(context);
+ initData(context);
+ }
+
+ /**
+ * Initializes class data
+ * @param context the context
+ */
+ private void initData(Context context) {
+ scroller = new WheelScroller(getContext(), scrollingListener);
+ }
+
+ // Scrolling listener
+ WheelScroller.ScrollingListener scrollingListener = new WheelScroller.ScrollingListener() {
+ public void onStarted() {
+ isScrollingPerformed = true;
+ notifyScrollingListenersAboutStart();
+ }
+
+ public void onScroll(int distance) {
+ doScroll(distance);
+
+ int height = getHeight();
+ if (scrollingOffset > height) {
+ scrollingOffset = height;
+ scroller.stopScrolling();
+ } else if (scrollingOffset < -height) {
+ scrollingOffset = -height;
+ scroller.stopScrolling();
+ }
+ }
+
+ public void onFinished() {
+ if (isScrollingPerformed) {
+ notifyScrollingListenersAboutEnd();
+ isScrollingPerformed = false;
+ }
+
+ scrollingOffset = 0;
+ invalidate();
+ }
+
+ public void onJustify() {
+ if (Math.abs(scrollingOffset) > WheelScroller.MIN_DELTA_FOR_SCROLLING) {
+ scroller.scroll(scrollingOffset, 0);
+ }
+ }
+ };
+
+ /**
+ * Set the the specified scrolling interpolator
+ * @param interpolator the interpolator
+ */
+ public void setInterpolator(Interpolator interpolator) {
+ scroller.setInterpolator(interpolator);
+ }
+
+ /**
+ * Gets count of visible items
+ *
+ * @return the count of visible items
+ */
+ public int getVisibleItems() {
+ return visibleItems;
+ }
+
+ /**
+ * Sets the desired count of visible items.
+ * Actual amount of visible items depends on wheel layout parameters.
+ * To apply changes and rebuild view call measure().
+ *
+ * @param count the desired count for visible items
+ */
+ public void setVisibleItems(int count) {
+ visibleItems = count;
+ }
+
+ /**
+ * Gets view adapter
+ * @return the view adapter
+ */
+ public WheelViewAdapter getViewAdapter() {
+ return viewAdapter;
+ }
+
+ // Adapter listener
+ private DataSetObserver dataObserver = new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ invalidateWheel(false);
+ }
+
+ @Override
+ public void onInvalidated() {
+ invalidateWheel(true);
+ }
+ };
+
+ /**
+ * Sets view adapter. Usually new adapters contain different views, so
+ * it needs to rebuild view by calling measure().
+ *
+ * @param viewAdapter the view adapter
+ */
+ public void setViewAdapter(WheelViewAdapter viewAdapter) {
+ if (this.viewAdapter != null) {
+ this.viewAdapter.unregisterDataSetObserver(dataObserver);
+ }
+ this.viewAdapter = viewAdapter;
+ if (this.viewAdapter != null) {
+ this.viewAdapter.registerDataSetObserver(dataObserver);
+ }
+
+ invalidateWheel(true);
+ }
+
+ /**
+ * Adds wheel changing listener
+ * @param listener the listener
+ */
+ public void addChangingListener(OnWheelChangedListener listener) {
+ changingListeners.add(listener);
+ }
+
+ /**
+ * Removes wheel changing listener
+ * @param listener the listener
+ */
+ public void removeChangingListener(OnWheelChangedListener listener) {
+ changingListeners.remove(listener);
+ }
+
+ /**
+ * Notifies changing listeners
+ * @param oldValue the old wheel value
+ * @param newValue the new wheel value
+ */
+ protected void notifyChangingListeners(int oldValue, int newValue) {
+ for (OnWheelChangedListener listener : changingListeners) {
+ listener.onChanged(this, oldValue, newValue);
+ }
+
+ // play a sound.
+ // TODO: something better
+ this.playSoundEffect(SoundEffectConstants.CLICK);
+ }
+
+ /**
+ * Adds wheel scrolling listener
+ * @param listener the listener
+ */
+ public void addScrollingListener(OnWheelScrollListener listener) {
+ scrollingListeners.add(listener);
+ }
+
+ /**
+ * Removes wheel scrolling listener
+ * @param listener the listener
+ */
+ public void removeScrollingListener(OnWheelScrollListener listener) {
+ scrollingListeners.remove(listener);
+ }
+
+ /**
+ * Notifies listeners about starting scrolling
+ */
+ protected void notifyScrollingListenersAboutStart() {
+ for (OnWheelScrollListener listener : scrollingListeners) {
+ listener.onScrollingStarted(this);
+ }
+ }
+
+ /**
+ * Notifies listeners about ending scrolling
+ */
+ protected void notifyScrollingListenersAboutEnd() {
+ for (OnWheelScrollListener listener : scrollingListeners) {
+ listener.onScrollingFinished(this);
+ }
+ }
+
+ /**
+ * Adds wheel clicking listener
+ * @param listener the listener
+ */
+ public void addClickingListener(OnWheelClickedListener listener) {
+ clickingListeners.add(listener);
+ }
+
+ /**
+ * Removes wheel clicking listener
+ * @param listener the listener
+ */
+ public void removeClickingListener(OnWheelClickedListener listener) {
+ clickingListeners.remove(listener);
+ }
+
+ /**
+ * Notifies listeners about clicking
+ */
+ protected void notifyClickListenersAboutClick(int item) {
+ for (OnWheelClickedListener listener : clickingListeners) {
+ listener.onItemClicked(this, item);
+ }
+ }
+
+ /**
+ * Gets current value
+ *
+ * @return the current value
+ */
+ public int getCurrentItem() {
+ return currentItem;
+ }
+
+ /**
+ * Sets the current item. Does nothing when index is wrong.
+ *
+ * @param index the item index
+ * @param animated the animation flag
+ */
+ public void setCurrentItem(int index, boolean animated) {
+ if (viewAdapter == null || viewAdapter.getItemsCount() == 0) {
+ return; // throw?
+ }
+
+ int itemCount = viewAdapter.getItemsCount();
+ if (index < 0 || index >= itemCount) {
+ if (isCyclic) {
+ while (index < 0) {
+ index += itemCount;
+ }
+ index %= itemCount;
+ } else{
+ return; // throw?
+ }
+ }
+ if (index != currentItem) {
+ if (animated) {
+ int itemsToScroll = index - currentItem;
+ if (isCyclic) {
+ int scroll = itemCount + Math.min(index, currentItem) - Math.max(index, currentItem);
+ if (scroll < Math.abs(itemsToScroll)) {
+ itemsToScroll = itemsToScroll < 0 ? scroll : -scroll;
+ }
+ }
+ scroll(itemsToScroll, 0);
+ } else {
+ scrollingOffset = 0;
+
+ int old = currentItem;
+ currentItem = index;
+
+ notifyChangingListeners(old, currentItem);
+
+ invalidate();
+ }
+ }
+ }
+
+ /**
+ * Sets the current item w/o animation. Does nothing when index is wrong.
+ *
+ * @param index the item index
+ */
+ public void setCurrentItem(int index) {
+ setCurrentItem(index, false);
+ }
+
+ /**
+ * Tests if wheel is cyclic. That means before the 1st item there is shown the last one
+ * @return true if wheel is cyclic
+ */
+ public boolean isCyclic() {
+ return isCyclic;
+ }
+
+ /**
+ * Set wheel cyclic flag
+ * @param isCyclic the flag to set
+ */
+ public void setCyclic(boolean isCyclic) {
+ this.isCyclic = isCyclic;
+ invalidateWheel(false);
+ }
+
+ /**
+ * Invalidates wheel
+ * @param clearCaches if true then cached views will be clear
+ */
+ public void invalidateWheel(boolean clearCaches) {
+ if (clearCaches) {
+ recycle.clearAll();
+ if (itemsLayout != null) {
+ itemsLayout.removeAllViews();
+ }
+ scrollingOffset = 0;
+ } else if (itemsLayout != null) {
+ // cache all items
+ recycle.recycleItems(itemsLayout, firstItem, new ItemsRange());
+ }
+
+ invalidate();
+ }
+
+ /**
+ * Initializes resources
+ */
+ private void initResourcesIfNecessary() {
+ if (centerDrawable == null) {
+ centerDrawable = getContext().getResources().getDrawable(R.drawable.wheel_val);
+ }
+
+ if (topShadow == null) {
+ topShadow = new GradientDrawable(Orientation.TOP_BOTTOM, SHADOWS_COLORS);
+ }
+
+ if (bottomShadow == null) {
+ bottomShadow = new GradientDrawable(Orientation.BOTTOM_TOP, SHADOWS_COLORS);
+ }
+
+ setBackgroundResource(R.drawable.wheel_bg);
+ }
+
+ /**
+ * Calculates desired height for layout
+ *
+ * @param layout
+ * the source layout
+ * @return the desired layout height
+ */
+ private int getDesiredHeight(LinearLayout layout) {
+ if (layout != null && layout.getChildAt(0) != null) {
+ itemHeight = layout.getChildAt(0).getMeasuredHeight();
+ }
+
+ int desired = itemHeight * visibleItems - itemHeight * ITEM_OFFSET_PERCENT / 50;
+
+ return Math.max(desired, getSuggestedMinimumHeight());
+ }
+
+ /**
+ * Returns height of wheel item
+ * @return the item height
+ */
+ private int getItemHeight() {
+ if (itemHeight != 0) {
+ return itemHeight;
+ }
+
+ if (itemsLayout != null && itemsLayout.getChildAt(0) != null) {
+ itemHeight = itemsLayout.getChildAt(0).getHeight();
+ return itemHeight;
+ }
+
+ return getHeight() / visibleItems;
+ }
+
+ /**
+ * Calculates control width and creates text layouts
+ * @param widthSize the input layout width
+ * @param mode the layout mode
+ * @return the calculated control width
+ */
+ private int calculateLayoutWidth(int widthSize, int mode) {
+ initResourcesIfNecessary();
+
+ // TODO: make it static
+ itemsLayout.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ itemsLayout.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.UNSPECIFIED),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ int width = itemsLayout.getMeasuredWidth();
+
+ if (mode == MeasureSpec.EXACTLY) {
+ width = widthSize;
+ } else {
+ width += 2 * PADDING;
+
+ // Check against our minimum width
+ width = Math.max(width, getSuggestedMinimumWidth());
+
+ if (mode == MeasureSpec.AT_MOST && widthSize < width) {
+ width = widthSize;
+ }
+ }
+
+ itemsLayout.measure(MeasureSpec.makeMeasureSpec(width - 2 * PADDING, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+
+ return width;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ buildViewForMeasuring();
+
+ int width = calculateLayoutWidth(widthSize, widthMode);
+
+ int height;
+ if (heightMode == MeasureSpec.EXACTLY) {
+ height = heightSize;
+ } else {
+ height = getDesiredHeight(itemsLayout);
+
+ if (heightMode == MeasureSpec.AT_MOST) {
+ height = Math.min(height, heightSize);
+ }
+ }
+
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ layout(r - l, b - t);
+ }
+
+ /**
+ * Sets layouts width and height
+ * @param width the layout width
+ * @param height the layout height
+ */
+ private void layout(int width, int height) {
+ int itemsWidth = width - 2 * PADDING;
+
+ itemsLayout.layout(0, 0, itemsWidth, height);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (viewAdapter != null && viewAdapter.getItemsCount() > 0) {
+ updateView();
+
+ drawItems(canvas);
+ drawCenterRect(canvas);
+ }
+
+// drawShadows(canvas);
+ }
+
+ /**
+ * Draws shadows on top and bottom of control
+ * @param canvas the canvas for drawing
+ */
+ private void drawShadows(Canvas canvas) {
+ int height = (int)(1.5 * getItemHeight());
+ topShadow.setBounds(0, 0, getWidth(), height);
+ topShadow.draw(canvas);
+
+ bottomShadow.setBounds(0, getHeight() - height, getWidth(), getHeight());
+ bottomShadow.draw(canvas);
+ }
+
+ /**
+ * Draws items
+ * @param canvas the canvas for drawing
+ */
+ private void drawItems(Canvas canvas) {
+ canvas.save();
+
+ int top = (currentItem - firstItem) * getItemHeight() + (getItemHeight() - getHeight()) / 2;
+ canvas.translate(PADDING, - top + scrollingOffset);
+
+ itemsLayout.draw(canvas);
+
+ canvas.restore();
+ }
+
+ /**
+ * Draws rect for current value
+ * @param canvas the canvas for drawing
+ */
+ private void drawCenterRect(Canvas canvas) {
+ int center = getHeight() / 2;
+ int offset = (int) (getItemHeight() / 2 * 1.2);
+ centerDrawable.setBounds(0, center - offset, getWidth(), center + offset);
+ centerDrawable.draw(canvas);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (!isEnabled() || getViewAdapter() == null) {
+ return true;
+ }
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_MOVE:
+ if (getParent() != null) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ if (!isScrollingPerformed) {
+ int distance = (int) event.getY() - getHeight() / 2;
+ if (distance > 0) {
+ distance += getItemHeight() / 2;
+ } else {
+ distance -= getItemHeight() / 2;
+ }
+ int items = distance / getItemHeight();
+ if (items != 0 && isValidItemIndex(currentItem + items)) {
+ notifyClickListenersAboutClick(currentItem + items);
+ }
+ }
+ break;
+ }
+
+ return scroller.onTouchEvent(event);
+ }
+
+ /**
+ * Scrolls the wheel
+ * @param delta the scrolling value
+ */
+ private void doScroll(int delta) {
+ scrollingOffset += delta;
+
+ int itemHeight = getItemHeight();
+ int count = scrollingOffset / itemHeight;
+
+ int pos = currentItem - count;
+ int itemCount = viewAdapter.getItemsCount();
+
+ int fixPos = scrollingOffset % itemHeight;
+ if (Math.abs(fixPos) <= itemHeight / 2) {
+ fixPos = 0;
+ }
+ if (isCyclic && itemCount > 0) {
+ if (fixPos > 0) {
+ pos--;
+ count++;
+ } else if (fixPos < 0) {
+ pos++;
+ count--;
+ }
+ // fix position by rotating
+ while (pos < 0) {
+ pos += itemCount;
+ }
+ pos %= itemCount;
+ } else {
+ //
+ if (pos < 0) {
+ count = currentItem;
+ pos = 0;
+ } else if (pos >= itemCount) {
+ count = currentItem - itemCount + 1;
+ pos = itemCount - 1;
+ } else if (pos > 0 && fixPos > 0) {
+ pos--;
+ count++;
+ } else if (pos < itemCount - 1 && fixPos < 0) {
+ pos++;
+ count--;
+ }
+ }
+
+ int offset = scrollingOffset;
+ if (pos != currentItem) {
+ setCurrentItem(pos, false);
+ } else {
+ invalidate();
+ }
+
+ // update offset
+ scrollingOffset = offset - count * itemHeight;
+ if (scrollingOffset > getHeight()) {
+ scrollingOffset = scrollingOffset % getHeight() + getHeight();
+ }
+ }
+
+ /**
+ * Scroll the wheel
+ * @param itemsToSkip items to scroll
+ * @param time scrolling duration
+ */
+ public void scroll(int itemsToScroll, int time) {
+ int distance = itemsToScroll * getItemHeight() - scrollingOffset;
+ scroller.scroll(distance, time);
+ }
+
+ /**
+ * Calculates range for wheel items
+ * @return the items range
+ */
+ private ItemsRange getItemsRange() {
+ if (getItemHeight() == 0) {
+ return null;
+ }
+
+ int first = currentItem;
+ int count = 1;
+
+ while (count * getItemHeight() < getHeight()) {
+ first--;
+ count += 2; // top + bottom items
+ }
+
+ if (scrollingOffset != 0) {
+ if (scrollingOffset > 0) {
+ first--;
+ }
+ count++;
+
+ // process empty items above the first or below the second
+ int emptyItems = scrollingOffset / getItemHeight();
+ first -= emptyItems;
+ count += Math.asin(emptyItems);
+ }
+ return new ItemsRange(first, count);
+ }
+
+ /**
+ * Rebuilds wheel items if necessary. Caches all unused items.
+ *
+ * @return true if items are rebuilt
+ */
+ private boolean rebuildItems() {
+ boolean updated = false;
+ ItemsRange range = getItemsRange();
+ if (itemsLayout != null) {
+ int first = recycle.recycleItems(itemsLayout, firstItem, range);
+ updated = firstItem != first;
+ firstItem = first;
+ } else {
+ createItemsLayout();
+ updated = true;
+ }
+
+ if (!updated) {
+ updated = firstItem != range.getFirst() || itemsLayout.getChildCount() != range.getCount();
+ }
+
+ if (firstItem > range.getFirst() && firstItem <= range.getLast()) {
+ for (int i = firstItem - 1; i >= range.getFirst(); i--) {
+ if (!addViewItem(i, true)) {
+ break;
+ }
+ firstItem = i;
+ }
+ } else {
+ firstItem = range.getFirst();
+ }
+
+ int first = firstItem;
+ for (int i = itemsLayout.getChildCount(); i < range.getCount(); i++) {
+ if (!addViewItem(firstItem + i, false) && itemsLayout.getChildCount() == 0) {
+ first++;
+ }
+ }
+ firstItem = first;
+
+ return updated;
+ }
+
+ /**
+ * Updates view. Rebuilds items and label if necessary, recalculate items sizes.
+ */
+ private void updateView() {
+ if (rebuildItems()) {
+ calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY);
+ layout(getWidth(), getHeight());
+ }
+ }
+
+ /**
+ * Creates item layouts if necessary
+ */
+ private void createItemsLayout() {
+ if (itemsLayout == null) {
+ itemsLayout = new LinearLayout(getContext());
+ itemsLayout.setOrientation(LinearLayout.VERTICAL);
+ }
+ }
+
+ /**
+ * Builds view for measuring
+ */
+ private void buildViewForMeasuring() {
+ // clear all items
+ if (itemsLayout != null) {
+ recycle.recycleItems(itemsLayout, firstItem, new ItemsRange());
+ } else {
+ createItemsLayout();
+ }
+
+ // add views
+ int addItems = visibleItems / 2;
+ for (int i = currentItem + addItems; i >= currentItem - addItems; i--) {
+ if (addViewItem(i, true)) {
+ firstItem = i;
+ }
+ }
+ }
+
+ /**
+ * Adds view for item to items layout
+ * @param index the item index
+ * @param first the flag indicates if view should be first
+ * @return true if corresponding item exists and is added
+ */
+ private boolean addViewItem(int index, boolean first) {
+ View view = getItemView(index);
+ if (view != null) {
+ if (first) {
+ itemsLayout.addView(view, 0);
+ } else {
+ itemsLayout.addView(view);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether intem index is valid
+ * @param index the item index
+ * @return true if item index is not out of bounds or the wheel is cyclic
+ */
+ private boolean isValidItemIndex(int index) {
+ return viewAdapter != null && viewAdapter.getItemsCount() > 0 &&
+ (isCyclic || index >= 0 && index < viewAdapter.getItemsCount());
+ }
+
+ /**
+ * Returns view for specified item
+ * @param index the item index
+ * @return item view or empty view if index is out of bounds
+ */
+ private View getItemView(int index) {
+ if (viewAdapter == null || viewAdapter.getItemsCount() == 0) {
+ return null;
+ }
+ int count = viewAdapter.getItemsCount();
+ if (!isValidItemIndex(index)) {
+ return viewAdapter.getEmptyItem(recycle.getEmptyItem(), itemsLayout);
+ } else {
+ while (index < 0) {
+ index = count + index;
+ }
+ }
+
+ index %= count;
+ return viewAdapter.getItem(index, recycle.getItem(), itemsLayout);
+ }
+
+ /**
+ * Stops scrolling
+ */
+ public void stopScrolling() {
+ scroller.stopScrolling();
+ }
+}
diff --git a/platform/java/src/kankan/wheel/widget/adapters/AbstractWheelAdapter.java b/platform/java/src/kankan/wheel/widget/adapters/AbstractWheelAdapter.java
new file mode 100644
index 00000000..8d7f1447
--- /dev/null
+++ b/platform/java/src/kankan/wheel/widget/adapters/AbstractWheelAdapter.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2011 Yuri Kanivets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kankan.wheel.widget.adapters;
+
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Abstract Wheel adapter.
+ */
+public abstract class AbstractWheelAdapter implements WheelViewAdapter {
+ // Observers
+ private List<DataSetObserver> datasetObservers;
+
+ @Override
+ public View getEmptyItem(View convertView, ViewGroup parent) {
+ return null;
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver observer) {
+ if (datasetObservers == null) {
+ datasetObservers = new LinkedList<DataSetObserver>();
+ }
+ datasetObservers.add(observer);
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ if (datasetObservers != null) {
+ datasetObservers.remove(observer);
+ }
+ }
+
+ /**
+ * Notifies observers about data changing
+ */
+ protected void notifyDataChangedEvent() {
+ if (datasetObservers != null) {
+ for (DataSetObserver observer : datasetObservers) {
+ observer.onChanged();
+ }
+ }
+ }
+
+ /**
+ * Notifies observers about invalidating data
+ */
+ protected void notifyDataInvalidatedEvent() {
+ if (datasetObservers != null) {
+ for (DataSetObserver observer : datasetObservers) {
+ observer.onInvalidated();
+ }
+ }
+ }
+}
diff --git a/platform/java/src/kankan/wheel/widget/adapters/AbstractWheelTextAdapter.java b/platform/java/src/kankan/wheel/widget/adapters/AbstractWheelTextAdapter.java
new file mode 100644
index 00000000..620c8e74
--- /dev/null
+++ b/platform/java/src/kankan/wheel/widget/adapters/AbstractWheelTextAdapter.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2011 Yuri Kanivets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package kankan.wheel.widget.adapters;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * Abstract wheel adapter provides common functionality for adapters.
+ */
+public abstract class AbstractWheelTextAdapter extends AbstractWheelAdapter {
+
+ /** Text view resource. Used as a default view for adapter. */
+ public static final int TEXT_VIEW_ITEM_RESOURCE = -1;
+
+ /** No resource constant. */
+ protected static final int NO_RESOURCE = 0;
+
+ /** Default text color */
+ public static final int DEFAULT_TEXT_COLOR = 0xFF101010;
+
+ /** Default text color */
+ public static final int LABEL_COLOR = 0xFF700070;
+
+ /** Default text size */
+ public static final int DEFAULT_TEXT_SIZE = 24;
+
+ // Text settings
+ private int textColor = DEFAULT_TEXT_COLOR;
+ private int textSize = DEFAULT_TEXT_SIZE;
+
+ // Current context
+ protected Context context;
+ // Layout inflater
+ protected LayoutInflater inflater;
+
+ // Items resources
+ protected int itemResourceId;
+ protected int itemTextResourceId;
+
+ // Empty items resources
+ protected int emptyItemResourceId;
+
+ /**
+ * Constructor
+ * @param context the current context
+ */
+ protected AbstractWheelTextAdapter(Context context) {
+ this(context, TEXT_VIEW_ITEM_RESOURCE);
+ }
+
+ /**
+ * Constructor
+ * @param context the current context
+ * @param itemResource the resource ID for a layout file containing a TextView to use when instantiating items views
+ */
+ protected AbstractWheelTextAdapter(Context context, int itemResource) {
+ this(context, itemResource, NO_RESOURCE);
+ }
+
+ /**
+ * Constructor
+ * @param context the current context
+ * @param itemResource the resource ID for a layout file containing a TextView to use when instantiating items views
+ * @param itemTextResource the resource ID for a text view in the item layout
+ */
+ protected AbstractWheelTextAdapter(Context context, int itemResource, int itemTextResource) {
+ this.context = context;
+ itemResourceId = itemResource;
+ itemTextResourceId = itemTextResource;
+
+ inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ /**
+ * Gets text color
+ * @return the text color
+ */
+ public int getTextColor() {
+ return textColor;
+ }
+
+ /**
+ * Sets text color
+ * @param textColor the text color to set
+ */
+ public void setTextColor(int textColor) {
+ this.textColor = textColor;
+ }
+
+ /**
+ * Gets text size
+ * @return the text size
+ */
+ public int getTextSize() {
+ return textSize;
+ }
+
+ /**
+ * Sets text size
+ * @param textSize the text size to set
+ */
+ public void setTextSize(int textSize) {
+ this.textSize = textSize;
+ }
+
+ /**
+ * Gets resource Id for items views
+ * @return the item resource Id
+ */
+ public int getItemResource() {
+ return itemResourceId;
+ }
+
+ /**
+ * Sets resource Id for items views
+ * @param itemResourceId the resource Id to set
+ */
+ public void setItemResource(int itemResourceId) {
+ this.itemResourceId = itemResourceId;
+ }
+
+ /**
+ * Gets resource Id for text view in item layout
+ * @return the item text resource Id
+ */
+ public int getItemTextResource() {
+ return itemTextResourceId;
+ }
+
+ /**
+ * Sets resource Id for text view in item layout
+ * @param itemTextResourceId the item text resource Id to set
+ */
+ public void setItemTextResource(int itemTextResourceId) {
+ this.itemTextResourceId = itemTextResourceId;
+ }
+
+ /**
+ * Gets resource Id for empty items views
+ * @return the empty item resource Id
+ */
+ public int getEmptyItemResource() {
+ return emptyItemResourceId;
+ }
+
+ /**
+ * Sets resource Id for empty items views
+ * @param emptyItemResourceId the empty item resource Id to set
+ */
+ public void setEmptyItemResource(int emptyItemResourceId) {
+ this.emptyItemResourceId = emptyItemResourceId;
+ }
+
+
+ /**
+ * Returns text for specified item
+ * @param index the item index
+ * @return the text of specified items
+ */
+ protected abstract CharSequence getItemText(int index);
+
+ @Override
+ public View getItem(int index, View convertView, ViewGroup parent) {
+ if (index >= 0 && index < getItemsCount()) {
+ if (convertView == null) {
+ convertView = getView(itemResourceId, parent);
+ }
+ TextView textView = getTextView(convertView, itemTextResourceId);
+ if (textView != null) {
+ CharSequence text = getItemText(index);
+ if (text == null) {
+ text = "";
+ }
+ textView.setText(text);
+
+ if (itemResourceId == TEXT_VIEW_ITEM_RESOURCE) {
+ configureTextView(textView);
+ }
+ }
+ return convertView;
+ }
+ return null;
+ }
+
+ @Override
+ public View getEmptyItem(View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = getView(emptyItemResourceId, parent);
+ }
+ if (emptyItemResourceId == TEXT_VIEW_ITEM_RESOURCE && convertView instanceof TextView) {
+ configureTextView((TextView)convertView);
+ }
+
+ return convertView;
+ }
+
+ /**
+ * Configures text view. Is called for the TEXT_VIEW_ITEM_RESOURCE views.
+ * @param view the text view to be configured
+ */
+ protected void configureTextView(TextView view) {
+ view.setTextColor(textColor);
+ view.setGravity(Gravity.CENTER);
+ view.setTextSize(textSize);
+ view.setLines(1);
+// view.setTypeface(Typeface.SANS_SERIF, Typeface.BOLD);
+ view.setTypeface(Typeface.SANS_SERIF, Typeface.NORMAL);
+ }
+
+ /**
+ * Loads a text view from view
+ * @param view the text view or layout containing it
+ * @param textResource the text resource Id in layout
+ * @return the loaded text view
+ */
+ private TextView getTextView(View view, int textResource) {
+ TextView text = null;
+ try {
+ if (textResource == NO_RESOURCE && view instanceof TextView) {
+ text = (TextView) view;
+ } else if (textResource != NO_RESOURCE) {
+ text = (TextView) view.findViewById(textResource);
+ }
+ } catch (ClassCastException e) {
+ Log.e("AbstractWheelAdapter", "You must supply a resource ID for a TextView");
+ throw new IllegalStateException(
+ "AbstractWheelAdapter requires the resource ID to be a TextView", e);
+ }
+
+ return text;
+ }
+
+ /**
+ * Loads view from resources
+ * @param resource the resource Id
+ * @return the loaded view or null if resource is not set
+ */
+ private View getView(int resource, ViewGroup parent) {
+ switch (resource) {
+ case NO_RESOURCE:
+ return null;
+ case TEXT_VIEW_ITEM_RESOURCE:
+ return new TextView(context);
+ default:
+ return inflater.inflate(resource, parent, false);
+ }
+ }
+}
diff --git a/platform/java/src/kankan/wheel/widget/adapters/AdapterWheel.java b/platform/java/src/kankan/wheel/widget/adapters/AdapterWheel.java
new file mode 100644
index 00000000..000001cd
--- /dev/null
+++ b/platform/java/src/kankan/wheel/widget/adapters/AdapterWheel.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 Yuri Kanivets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kankan.wheel.widget.adapters;
+
+import android.content.Context;
+
+import kankan.wheel.widget.WheelAdapter;
+
+/**
+ * Adapter class for old wheel adapter (deprecated WheelAdapter class).
+ *
+ * @deprecated Will be removed soon
+ */
+public class AdapterWheel extends AbstractWheelTextAdapter {
+
+ // Source adapter
+ private WheelAdapter adapter;
+
+ /**
+ * Constructor
+ * @param context the current context
+ * @param adapter the source adapter
+ */
+ public AdapterWheel(Context context, WheelAdapter adapter) {
+ super(context);
+
+ this.adapter = adapter;
+ }
+
+ /**
+ * Gets original adapter
+ * @return the original adapter
+ */
+ public WheelAdapter getAdapter() {
+ return adapter;
+ }
+
+ @Override
+ public int getItemsCount() {
+ return adapter.getItemsCount();
+ }
+
+ @Override
+ protected CharSequence getItemText(int index) {
+ return adapter.getItem(index);
+ }
+
+}
diff --git a/platform/java/src/kankan/wheel/widget/adapters/ArrayWheelAdapter.java b/platform/java/src/kankan/wheel/widget/adapters/ArrayWheelAdapter.java
new file mode 100644
index 00000000..c9d430d7
--- /dev/null
+++ b/platform/java/src/kankan/wheel/widget/adapters/ArrayWheelAdapter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 Yuri Kanivets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package kankan.wheel.widget.adapters;
+
+import android.content.Context;
+
+/**
+ * The simple Array wheel adapter
+ * @param <T> the element type
+ */
+public class ArrayWheelAdapter<T> extends AbstractWheelTextAdapter {
+
+ // items
+ private T items[];
+
+ /**
+ * Constructor
+ * @param context the current context
+ * @param items the items
+ */
+ public ArrayWheelAdapter(Context context, T items[]) {
+ super(context);
+
+ //setEmptyItemResource(TEXT_VIEW_ITEM_RESOURCE);
+ this.items = items;
+ }
+
+ public void setItems(T items[])
+ {
+ this.items = items;
+ }
+
+ @Override
+ public CharSequence getItemText(int index) {
+ if (index >= 0 && index < items.length) {
+ T item = items[index];
+ if (item instanceof CharSequence) {
+ return (CharSequence) item;
+ }
+ return item.toString();
+ }
+ return null;
+ }
+
+ @Override
+ public int getItemsCount() {
+ return items.length;
+ }
+}
diff --git a/platform/java/src/kankan/wheel/widget/adapters/ArrayWheelAdapterColor.java b/platform/java/src/kankan/wheel/widget/adapters/ArrayWheelAdapterColor.java
new file mode 100644
index 00000000..cf4a9954
--- /dev/null
+++ b/platform/java/src/kankan/wheel/widget/adapters/ArrayWheelAdapterColor.java
@@ -0,0 +1,50 @@
+package kankan.wheel.widget.adapters;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.widget.TextView;
+
+public class ArrayWheelAdapterColor<T> extends AbstractWheelTextAdapter {
+
+ // items
+ private T items[];
+
+ public ArrayWheelAdapterColor(Context context, T items[]) {
+ super(context);
+
+ this.items = items;
+ }
+
+ @Override
+ public CharSequence getItemText(int index) {
+ if (index >= 0 && index < items.length) {
+ T item = items[index];
+ if (item instanceof CharSequence) {
+ return (CharSequence) item;
+ }
+ return item.toString();
+ }
+ return null;
+ }
+
+ @Override
+ public int getItemsCount() {
+ return items.length;
+ }
+
+ @Override
+ protected void configureTextView(TextView view) {
+ super.configureTextView(view);
+
+ // if the text ends with "(red)"
+ // color it red.
+ String text = view.getText().toString();
+ if (text.endsWith("(red)"))
+ {
+ text = text.replace("(red)","");
+ view.setText(text);
+ view.setTextColor(Color.parseColor("#ff0000"));
+ }
+ }
+
+} \ No newline at end of file
diff --git a/platform/java/src/kankan/wheel/widget/adapters/NumericWheelAdapter.java b/platform/java/src/kankan/wheel/widget/adapters/NumericWheelAdapter.java
new file mode 100644
index 00000000..ec8b9c86
--- /dev/null
+++ b/platform/java/src/kankan/wheel/widget/adapters/NumericWheelAdapter.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 Yuri Kanivets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kankan.wheel.widget.adapters;
+
+import android.content.Context;
+
+/**
+ * Numeric Wheel adapter.
+ */
+public class NumericWheelAdapter extends AbstractWheelTextAdapter {
+
+ /** The default min value */
+ public static final int DEFAULT_MAX_VALUE = 9;
+
+ /** The default max value */
+ private static final int DEFAULT_MIN_VALUE = 0;
+
+ // Values
+ private int minValue;
+ private int maxValue;
+
+ // format
+ private String format;
+
+ /**
+ * Constructor
+ * @param context the current context
+ */
+ public NumericWheelAdapter(Context context) {
+ this(context, DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE);
+ }
+
+ /**
+ * Constructor
+ * @param context the current context
+ * @param minValue the wheel min value
+ * @param maxValue the wheel max value
+ */
+ public NumericWheelAdapter(Context context, int minValue, int maxValue) {
+ this(context, minValue, maxValue, null);
+ }
+
+ /**
+ * Constructor
+ * @param context the current context
+ * @param minValue the wheel min value
+ * @param maxValue the wheel max value
+ * @param format the format string
+ */
+ public NumericWheelAdapter(Context context, int minValue, int maxValue, String format) {
+ super(context);
+
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ this.format = format;
+ }
+
+ @Override
+ public CharSequence getItemText(int index) {
+ if (index >= 0 && index < getItemsCount()) {
+ int value = minValue + index;
+ return format != null ? String.format(format, value) : Integer.toString(value);
+ }
+ return null;
+ }
+
+ @Override
+ public int getItemsCount() {
+ return maxValue - minValue + 1;
+ }
+}
diff --git a/platform/java/src/kankan/wheel/widget/adapters/WheelViewAdapter.java b/platform/java/src/kankan/wheel/widget/adapters/WheelViewAdapter.java
new file mode 100644
index 00000000..db256570
--- /dev/null
+++ b/platform/java/src/kankan/wheel/widget/adapters/WheelViewAdapter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2011 Yuri Kanivets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kankan.wheel.widget.adapters;
+
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Wheel items adapter interface
+ */
+public interface WheelViewAdapter {
+ /**
+ * Gets items count
+ * @return the count of wheel items
+ */
+ public int getItemsCount();
+
+ /**
+ * Get a View that displays the data at the specified position in the data set
+ *
+ * @param index the item index
+ * @param convertView the old view to reuse if possible
+ * @param parent the parent that this view will eventually be attached to
+ * @return the wheel item View
+ */
+ public View getItem(int index, View convertView, ViewGroup parent);
+
+ /**
+ * Get a View that displays an empty wheel item placed before the first or after
+ * the last wheel item.
+ *
+ * @param convertView the old view to reuse if possible
+ * @param parent the parent that this view will eventually be attached to
+ * @return the empty item View
+ */
+ public View getEmptyItem(View convertView, ViewGroup parent);
+
+ /**
+ * Register an observer that is called when changes happen to the data used by this adapter.
+ * @param observer the observer to be registered
+ */
+ public void registerDataSetObserver(DataSetObserver observer);
+
+ /**
+ * Unregister an observer that has previously been registered
+ * @param observer the observer to be unregistered
+ */
+ void unregisterDataSetObserver (DataSetObserver observer);
+}