diff --git a/README b/README
index 6c9f6040..2f989bf7 100644
--- a/README
+++ b/README
@@ -34,4 +34,7 @@ Some code/libraries are used in the project:
android-xmlrpc
pskink et al. (Apache License, Version 2.0)
http://code.google.com/p/android-xmlrpc/
+ android-ColorPickerPreference
+ Daniel Nilsson and Sergey Margaritov (Apache License, Version 2.0)
+ https://github.com/attenzione/android-ColorPickerPreference
diff --git a/android/res/layout-land/dialog_color_picker.xml b/android/res/layout-land/dialog_color_picker.xml
new file mode 100644
index 00000000..e326d7e7
--- /dev/null
+++ b/android/res/layout-land/dialog_color_picker.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/res/layout/dialog_color_picker.xml b/android/res/layout/dialog_color_picker.xml
new file mode 100644
index 00000000..8ba75f2a
--- /dev/null
+++ b/android/res/layout/dialog_color_picker.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml
index 034f64de..c25dcf0c 100644
--- a/android/res/values/strings.xml
+++ b/android/res/values/strings.xml
@@ -216,6 +216,8 @@
Notification sound to play with alarm
Enable vibration
Vibrate on alarm
+Notification LED colour
+If supported by your device
Check RSS feeds
Alarm when new torrents are available
Enable ADW notifications
@@ -324,6 +326,9 @@
Loading RSS feed
Connected, but the RSS feed is empty
+Color Picker
+Press on Color to apply
+
Error during communication with server
Error building request
Error parsing of server response (please check your settings)
diff --git a/android/src/net/margaritov/preference/colorpicker/AlphaPatternDrawable.java b/android/src/net/margaritov/preference/colorpicker/AlphaPatternDrawable.java
new file mode 100644
index 00000000..60947a4a
--- /dev/null
+++ b/android/src/net/margaritov/preference/colorpicker/AlphaPatternDrawable.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ *
+ * 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 net.margaritov.preference.colorpicker;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+/**
+ * This drawable that draws a simple white and gray chessboard pattern.
+ * It's pattern you will often see as a background behind a
+ * partly transparent image in many applications.
+ * @author Daniel Nilsson
+ */
+public class AlphaPatternDrawable extends Drawable {
+
+ private int mRectangleSize = 10;
+
+ private Paint mPaint = new Paint();
+ private Paint mPaintWhite = new Paint();
+ private Paint mPaintGray = new Paint();
+
+ private int numRectanglesHorizontal;
+ private int numRectanglesVertical;
+
+ /**
+ * Bitmap in which the pattern will be cahched.
+ */
+ private Bitmap mBitmap;
+
+ public AlphaPatternDrawable(int rectangleSize) {
+ mRectangleSize = rectangleSize;
+ mPaintWhite.setColor(0xffffffff);
+ mPaintGray.setColor(0xffcbcbcb);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ throw new UnsupportedOperationException("Alpha is not supported by this drawwable.");
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ throw new UnsupportedOperationException("ColorFilter is not supported by this drawwable.");
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+
+ int height = bounds.height();
+ int width = bounds.width();
+
+ numRectanglesHorizontal = (int) Math.ceil((width / mRectangleSize));
+ numRectanglesVertical = (int) Math.ceil(height / mRectangleSize);
+
+ generatePatternBitmap();
+
+ }
+
+ /**
+ * This will generate a bitmap with the pattern
+ * as big as the rectangle we were allow to draw on.
+ * We do this to chache the bitmap so we don't need to
+ * recreate it each time draw() is called since it
+ * takes a few milliseconds.
+ */
+ private void generatePatternBitmap(){
+
+ if(getBounds().width() <= 0 || getBounds().height() <= 0){
+ return;
+ }
+
+ mBitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888);
+ Canvas canvas = new Canvas(mBitmap);
+
+ Rect r = new Rect();
+ boolean verticalStartWhite = true;
+ for (int i = 0; i <= numRectanglesVertical; i++) {
+
+ boolean isWhite = verticalStartWhite;
+ for (int j = 0; j <= numRectanglesHorizontal; j++) {
+
+ r.top = i * mRectangleSize;
+ r.left = j * mRectangleSize;
+ r.bottom = r.top + mRectangleSize;
+ r.right = r.left + mRectangleSize;
+
+ canvas.drawRect(r, isWhite ? mPaintWhite : mPaintGray);
+
+ isWhite = !isWhite;
+ }
+
+ verticalStartWhite = !verticalStartWhite;
+
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/android/src/net/margaritov/preference/colorpicker/ColorPickerDialog.java b/android/src/net/margaritov/preference/colorpicker/ColorPickerDialog.java
new file mode 100644
index 00000000..63840bca
--- /dev/null
+++ b/android/src/net/margaritov/preference/colorpicker/ColorPickerDialog.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ *
+ * 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 net.margaritov.preference.colorpicker;
+
+import org.transdroid.R;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+
+public class ColorPickerDialog
+ extends
+ Dialog
+ implements
+ ColorPickerView.OnColorChangedListener,
+ View.OnClickListener {
+
+ private ColorPickerView mColorPicker;
+
+ private ColorPickerPanelView mOldColor;
+ private ColorPickerPanelView mNewColor;
+
+ private OnColorChangedListener mListener;
+
+ public interface OnColorChangedListener {
+ public void onColorChanged(int color);
+ }
+
+ public ColorPickerDialog(Context context, int initialColor) {
+ super(context);
+
+ init(initialColor);
+ }
+
+ private void init(int color) {
+ // To fight color branding.
+ getWindow().setFormat(PixelFormat.RGBA_8888);
+
+ setUp(color);
+
+ }
+
+ private void setUp(int color) {
+
+ LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ View layout = inflater.inflate(R.layout.dialog_color_picker, null);
+
+ setContentView(layout);
+
+ setTitle(R.string.dialog_color_picker);
+
+ mColorPicker = (ColorPickerView) layout.findViewById(R.id.color_picker_view);
+ mOldColor = (ColorPickerPanelView) layout.findViewById(R.id.old_color_panel);
+ mNewColor = (ColorPickerPanelView) layout.findViewById(R.id.new_color_panel);
+
+ ((LinearLayout) mOldColor.getParent()).setPadding(
+ Math.round(mColorPicker.getDrawingOffset()),
+ 0,
+ Math.round(mColorPicker.getDrawingOffset()),
+ 0
+ );
+
+ mOldColor.setOnClickListener(this);
+ mNewColor.setOnClickListener(this);
+ mColorPicker.setOnColorChangedListener(this);
+ mOldColor.setColor(color);
+ mColorPicker.setColor(color, true);
+
+ }
+
+ @Override
+ public void onColorChanged(int color) {
+
+ mNewColor.setColor(color);
+
+ /*
+ if (mListener != null) {
+ mListener.onColorChanged(color);
+ }
+ */
+
+ }
+
+ public void setAlphaSliderVisible(boolean visible) {
+ mColorPicker.setAlphaSliderVisible(visible);
+ }
+
+ /**
+ * Set a OnColorChangedListener to get notified when the color
+ * selected by the user has changed.
+ * @param listener
+ */
+ public void setOnColorChangedListener(OnColorChangedListener listener){
+ mListener = listener;
+ }
+
+ public int getColor() {
+ return mColorPicker.getColor();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.new_color_panel) {
+ if (mListener != null) {
+ mListener.onColorChanged(mNewColor.getColor());
+ }
+ }
+ dismiss();
+ }
+
+}
diff --git a/android/src/net/margaritov/preference/colorpicker/ColorPickerPanelView.java b/android/src/net/margaritov/preference/colorpicker/ColorPickerPanelView.java
new file mode 100644
index 00000000..4011b5ff
--- /dev/null
+++ b/android/src/net/margaritov/preference/colorpicker/ColorPickerPanelView.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ *
+ * 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 net.margaritov.preference.colorpicker;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * This class draws a panel which which will be filled with a color which can be set.
+ * It can be used to show the currently selected color which you will get from
+ * the {@link ColorPickerView}.
+ * @author Daniel Nilsson
+ *
+ */
+public class ColorPickerPanelView extends View {
+
+ /**
+ * The width in pixels of the border
+ * surrounding the color panel.
+ */
+ private final static float BORDER_WIDTH_PX = 1;
+
+ private float mDensity = 1f;
+
+ private int mBorderColor = 0xff6E6E6E;
+ private int mColor = 0xff000000;
+
+ private Paint mBorderPaint;
+ private Paint mColorPaint;
+
+ private RectF mDrawingRect;
+ private RectF mColorRect;
+
+ private AlphaPatternDrawable mAlphaPattern;
+
+
+ public ColorPickerPanelView(Context context){
+ this(context, null);
+ }
+
+ public ColorPickerPanelView(Context context, AttributeSet attrs){
+ this(context, attrs, 0);
+ }
+
+ public ColorPickerPanelView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ private void init(){
+ mBorderPaint = new Paint();
+ mColorPaint = new Paint();
+ mDensity = getContext().getResources().getDisplayMetrics().density;
+ }
+
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+
+ final RectF rect = mColorRect;
+
+ if(BORDER_WIDTH_PX > 0){
+ mBorderPaint.setColor(mBorderColor);
+ canvas.drawRect(mDrawingRect, mBorderPaint);
+ }
+
+ if(mAlphaPattern != null){
+ mAlphaPattern.draw(canvas);
+ }
+
+ mColorPaint.setColor(mColor);
+
+ canvas.drawRect(rect, mColorPaint);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ mDrawingRect = new RectF();
+ mDrawingRect.left = getPaddingLeft();
+ mDrawingRect.right = w - getPaddingRight();
+ mDrawingRect.top = getPaddingTop();
+ mDrawingRect.bottom = h - getPaddingBottom();
+
+ setUpColorRect();
+
+ }
+
+ private void setUpColorRect(){
+ final RectF dRect = mDrawingRect;
+
+ float left = dRect.left + BORDER_WIDTH_PX;
+ float top = dRect.top + BORDER_WIDTH_PX;
+ float bottom = dRect.bottom - BORDER_WIDTH_PX;
+ float right = dRect.right - BORDER_WIDTH_PX;
+
+ mColorRect = new RectF(left,top, right, bottom);
+
+ mAlphaPattern = new AlphaPatternDrawable((int)(5 * mDensity));
+
+ mAlphaPattern.setBounds(
+ Math.round(mColorRect.left),
+ Math.round(mColorRect.top),
+ Math.round(mColorRect.right),
+ Math.round(mColorRect.bottom)
+ );
+
+ }
+
+ /**
+ * Set the color that should be shown by this view.
+ * @param color
+ */
+ public void setColor(int color){
+ mColor = color;
+ invalidate();
+ }
+
+ /**
+ * Get the color currently show by this view.
+ * @return
+ */
+ public int getColor(){
+ return mColor;
+ }
+
+ /**
+ * Set the color of the border surrounding the panel.
+ * @param color
+ */
+ public void setBorderColor(int color){
+ mBorderColor = color;
+ invalidate();
+ }
+
+ /**
+ * Get the color of the border surrounding the panel.
+ */
+ public int getBorderColor(){
+ return mBorderColor;
+ }
+
+}
\ No newline at end of file
diff --git a/android/src/net/margaritov/preference/colorpicker/ColorPickerPreference.java b/android/src/net/margaritov/preference/colorpicker/ColorPickerPreference.java
new file mode 100644
index 00000000..1b2d831e
--- /dev/null
+++ b/android/src/net/margaritov/preference/colorpicker/ColorPickerPreference.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2011 Sergey Margaritov
+ *
+ * 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 net.margaritov.preference.colorpicker;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Bitmap.Config;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+/**
+ * A preference type that allows a user to choose a time
+ * @author Sergey Margaritov
+ */
+public class ColorPickerPreference
+ extends
+ Preference
+ implements
+ Preference.OnPreferenceClickListener,
+ ColorPickerDialog.OnColorChangedListener {
+
+ View mView;
+ int mDefaultValue = Color.BLACK;
+ private int mValue = Color.BLACK;
+ private float mDensity = 0;
+ private boolean mAlphaSliderEnabled = false;
+
+ private static final String androidns = "http://schemas.android.com/apk/res/android";
+
+ public ColorPickerPreference(Context context) {
+ super(context);
+ init(context, null);
+ }
+
+ public ColorPickerPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs);
+ }
+
+ public ColorPickerPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context, attrs);
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ onColorChanged(restoreValue ? getValue() : (Integer) defaultValue);
+ }
+
+ private void init(Context context, AttributeSet attrs) {
+ mDensity = getContext().getResources().getDisplayMetrics().density;
+ setOnPreferenceClickListener(this);
+ if (attrs != null) {
+ String defaultValue = attrs.getAttributeValue(androidns, "defaultValue");
+ if (defaultValue.startsWith("#")) {
+ try {
+ mDefaultValue = convertToColorInt(defaultValue);
+ } catch (NumberFormatException e) {
+ Log.e("ColorPickerPreference", "Wrong color: " + defaultValue);
+ mDefaultValue = convertToColorInt("#FF000000");
+ }
+ } else {
+ int resourceId = attrs.getAttributeResourceValue(androidns, "defaultValue", 0);
+ if (resourceId != 0) {
+ mDefaultValue = context.getResources().getInteger(resourceId);
+ }
+ }
+ mAlphaSliderEnabled = attrs.getAttributeBooleanValue(null, "alphaSlider", false);
+ }
+ mValue = mDefaultValue;
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+ mView = view;
+ setPreviewColor();
+ }
+
+ private void setPreviewColor() {
+ if (mView == null) return;
+ ImageView iView = new ImageView(getContext());
+ LinearLayout widgetFrameView = ((LinearLayout)mView.findViewById(android.R.id.widget_frame));
+ if (widgetFrameView == null) return;
+ widgetFrameView.setVisibility(View.VISIBLE);
+ widgetFrameView.setPadding(
+ widgetFrameView.getPaddingLeft(),
+ widgetFrameView.getPaddingTop(),
+ (int)(mDensity * 8),
+ widgetFrameView.getPaddingBottom()
+ );
+ // remove already create preview image
+ int count = widgetFrameView.getChildCount();
+ if (count > 0) {
+ widgetFrameView.removeViews(0, count);
+ }
+ widgetFrameView.addView(iView);
+ iView.setBackgroundDrawable(new AlphaPatternDrawable((int)(5 * mDensity)));
+ iView.setImageBitmap(getPreviewBitmap());
+ }
+
+ private Bitmap getPreviewBitmap() {
+ int d = (int) (mDensity * 31); //30dip
+ int color = getValue();
+ Bitmap bm = Bitmap.createBitmap(d, d, Config.ARGB_8888);
+ int w = bm.getWidth();
+ int h = bm.getHeight();
+ int c = color;
+ for (int i = 0; i < w; i++) {
+ for (int j = i; j < h; j++) {
+ c = (i <= 1 || j <= 1 || i >= w-2 || j >= h-2) ? Color.GRAY : color;
+ bm.setPixel(i, j, c);
+ if (i != j) {
+ bm.setPixel(j, i, c);
+ }
+ }
+ }
+
+ return bm;
+ }
+
+ public int getValue() {
+ try {
+ if (isPersistent()) {
+ mValue = getPersistedInt(mDefaultValue);
+ }
+ } catch (ClassCastException e) {
+ mValue = mDefaultValue;
+ }
+
+ return mValue;
+ }
+
+ @Override
+ public void onColorChanged(int color) {
+ if (isPersistent()) {
+ persistInt(color);
+ }
+ mValue = color;
+ setPreviewColor();
+ try {
+ getOnPreferenceChangeListener().onPreferenceChange(this, color);
+ } catch (NullPointerException e) {
+
+ }
+ }
+
+ public boolean onPreferenceClick(Preference preference) {
+ ColorPickerDialog picker = new ColorPickerDialog(getContext(), getValue());
+ picker.setOnColorChangedListener(this);
+ if (mAlphaSliderEnabled) {
+ picker.setAlphaSliderVisible(true);
+ }
+ picker.show();
+
+ return false;
+ }
+
+ /**
+ * Toggle Alpha Slider visibility (by default it's disabled)
+ * @param enable
+ */
+ public void setAlphaSliderEnabled(boolean enable) {
+ mAlphaSliderEnabled = enable;
+ }
+
+ /**
+ * For custom purposes. Not used by ColorPickerPreferrence
+ * @param color
+ * @author Unknown
+ */
+ public static String convertToARGB(int color) {
+ String alpha = Integer.toHexString(Color.alpha(color));
+ String red = Integer.toHexString(Color.red(color));
+ String green = Integer.toHexString(Color.green(color));
+ String blue = Integer.toHexString(Color.blue(color));
+
+ if (alpha.length() == 1) {
+ alpha = "0" + alpha;
+ }
+
+ if (red.length() == 1) {
+ red = "0" + red;
+ }
+
+ if (green.length() == 1) {
+ green = "0" + green;
+ }
+
+ if (blue.length() == 1) {
+ blue = "0" + blue;
+ }
+
+ return "#" + alpha + red + green + blue;
+ }
+
+ /**
+ * For custom purposes. Not used by ColorPickerPreferrence
+ * @param argb
+ * @throws NumberFormatException
+ * @author Unknown
+ */
+ public static int convertToColorInt(String argb) throws NumberFormatException {
+
+ if (argb.startsWith("#")) {
+ argb = argb.replace("#", "");
+ }
+
+ int alpha = -1, red = -1, green = -1, blue = -1;
+
+ if (argb.length() == 8) {
+ alpha = Integer.parseInt(argb.substring(0, 2), 16);
+ red = Integer.parseInt(argb.substring(2, 4), 16);
+ green = Integer.parseInt(argb.substring(4, 6), 16);
+ blue = Integer.parseInt(argb.substring(6, 8), 16);
+ }
+ else if (argb.length() == 6) {
+ alpha = 255;
+ red = Integer.parseInt(argb.substring(0, 2), 16);
+ green = Integer.parseInt(argb.substring(2, 4), 16);
+ blue = Integer.parseInt(argb.substring(4, 6), 16);
+ }
+
+ return Color.argb(alpha, red, green, blue);
+ }
+
+}
\ No newline at end of file
diff --git a/android/src/net/margaritov/preference/colorpicker/ColorPickerView.java b/android/src/net/margaritov/preference/colorpicker/ColorPickerView.java
new file mode 100644
index 00000000..32e81dbd
--- /dev/null
+++ b/android/src/net/margaritov/preference/colorpicker/ColorPickerView.java
@@ -0,0 +1,952 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ *
+ * 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 net.margaritov.preference.colorpicker;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ComposeShader;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.graphics.Shader.TileMode;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Displays a color picker to the user and allow them
+ * to select a color. A slider for the alpha channel is
+ * also available. Enable it by setting
+ * setAlphaSliderVisible(boolean) to true.
+ * @author Daniel Nilsson
+ */
+public class ColorPickerView extends View {
+
+ private final static int PANEL_SAT_VAL = 0;
+ private final static int PANEL_HUE = 1;
+ private final static int PANEL_ALPHA = 2;
+
+ /**
+ * The width in pixels of the border
+ * surrounding all color panels.
+ */
+ private final static float BORDER_WIDTH_PX = 1;
+
+ /**
+ * The width in dp of the hue panel.
+ */
+ private float HUE_PANEL_WIDTH = 30f;
+ /**
+ * The height in dp of the alpha panel
+ */
+ private float ALPHA_PANEL_HEIGHT = 20f;
+ /**
+ * The distance in dp between the different
+ * color panels.
+ */
+ private float PANEL_SPACING = 10f;
+ /**
+ * The radius in dp of the color palette tracker circle.
+ */
+ private float PALETTE_CIRCLE_TRACKER_RADIUS = 5f;
+ /**
+ * The dp which the tracker of the hue or alpha panel
+ * will extend outside of its bounds.
+ */
+ private float RECTANGLE_TRACKER_OFFSET = 2f;
+
+
+ private float mDensity = 1f;
+
+ private OnColorChangedListener mListener;
+
+ private Paint mSatValPaint;
+ private Paint mSatValTrackerPaint;
+
+ private Paint mHuePaint;
+ private Paint mHueTrackerPaint;
+
+ private Paint mAlphaPaint;
+ private Paint mAlphaTextPaint;
+
+ private Paint mBorderPaint;
+
+ private Shader mValShader;
+ private Shader mSatShader;
+ private Shader mHueShader;
+ private Shader mAlphaShader;
+
+ private int mAlpha = 0xff;
+ private float mHue = 360f;
+ private float mSat = 0f;
+ private float mVal = 0f;
+
+ private String mAlphaSliderText = "";
+ private int mSliderTrackerColor = 0xff1c1c1c;
+ private int mBorderColor = 0xff6E6E6E;
+ private boolean mShowAlphaPanel = false;
+
+ /*
+ * To remember which panel that has the "focus" when
+ * processing hardware button data.
+ */
+ private int mLastTouchedPanel = PANEL_SAT_VAL;
+
+ /**
+ * Offset from the edge we must have or else
+ * the finger tracker will get clipped when
+ * it is drawn outside of the view.
+ */
+ private float mDrawingOffset;
+
+
+ /*
+ * Distance form the edges of the view
+ * of where we are allowed to draw.
+ */
+ private RectF mDrawingRect;
+
+ private RectF mSatValRect;
+ private RectF mHueRect;
+ private RectF mAlphaRect;
+
+ private AlphaPatternDrawable mAlphaPattern;
+
+ private Point mStartTouchPoint = null;
+
+ public interface OnColorChangedListener {
+ public void onColorChanged(int color);
+ }
+
+ public ColorPickerView(Context context){
+ this(context, null);
+ }
+
+ public ColorPickerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ColorPickerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ private void init(){
+ mDensity = getContext().getResources().getDisplayMetrics().density;
+ PALETTE_CIRCLE_TRACKER_RADIUS *= mDensity;
+ RECTANGLE_TRACKER_OFFSET *= mDensity;
+ HUE_PANEL_WIDTH *= mDensity;
+ ALPHA_PANEL_HEIGHT *= mDensity;
+ PANEL_SPACING = PANEL_SPACING * mDensity;
+
+ mDrawingOffset = calculateRequiredOffset();
+
+ initPaintTools();
+
+ //Needed for receiving trackball motion events.
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+ }
+
+ private void initPaintTools(){
+
+ mSatValPaint = new Paint();
+ mSatValTrackerPaint = new Paint();
+ mHuePaint = new Paint();
+ mHueTrackerPaint = new Paint();
+ mAlphaPaint = new Paint();
+ mAlphaTextPaint = new Paint();
+ mBorderPaint = new Paint();
+
+
+ mSatValTrackerPaint.setStyle(Style.STROKE);
+ mSatValTrackerPaint.setStrokeWidth(2f * mDensity);
+ mSatValTrackerPaint.setAntiAlias(true);
+
+ mHueTrackerPaint.setColor(mSliderTrackerColor);
+ mHueTrackerPaint.setStyle(Style.STROKE);
+ mHueTrackerPaint.setStrokeWidth(2f * mDensity);
+ mHueTrackerPaint.setAntiAlias(true);
+
+ mAlphaTextPaint.setColor(0xff1c1c1c);
+ mAlphaTextPaint.setTextSize(14f * mDensity);
+ mAlphaTextPaint.setAntiAlias(true);
+ mAlphaTextPaint.setTextAlign(Align.CENTER);
+ mAlphaTextPaint.setFakeBoldText(true);
+
+
+ }
+
+ private float calculateRequiredOffset(){
+ float offset = Math.max(PALETTE_CIRCLE_TRACKER_RADIUS, RECTANGLE_TRACKER_OFFSET);
+ offset = Math.max(offset, BORDER_WIDTH_PX * mDensity);
+
+ return offset * 1.5f;
+ }
+
+ private int[] buildHueColorArray(){
+
+ int[] hue = new int[361];
+
+ int count = 0;
+ for(int i = hue.length -1; i >= 0; i--, count++){
+ hue[count] = Color.HSVToColor(new float[]{i, 1f, 1f});
+ }
+
+ return hue;
+ }
+
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+
+ if(mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) return;
+
+ drawSatValPanel(canvas);
+ drawHuePanel(canvas);
+ drawAlphaPanel(canvas);
+
+ }
+
+ private void drawSatValPanel(Canvas canvas){
+
+ final RectF rect = mSatValRect;
+
+ if(BORDER_WIDTH_PX > 0){
+ mBorderPaint.setColor(mBorderColor);
+ canvas.drawRect(mDrawingRect.left, mDrawingRect.top, rect.right + BORDER_WIDTH_PX, rect.bottom + BORDER_WIDTH_PX, mBorderPaint);
+ }
+
+ if (mValShader == null) {
+ mValShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom,
+ 0xffffffff, 0xff000000, TileMode.CLAMP);
+ }
+
+ int rgb = Color.HSVToColor(new float[]{mHue,1f,1f});
+
+ mSatShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
+ 0xffffffff, rgb, TileMode.CLAMP);
+ ComposeShader mShader = new ComposeShader(mValShader, mSatShader, PorterDuff.Mode.MULTIPLY);
+ mSatValPaint.setShader(mShader);
+
+ canvas.drawRect(rect, mSatValPaint);
+
+ Point p = satValToPoint(mSat, mVal);
+
+ mSatValTrackerPaint.setColor(0xff000000);
+ canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS - 1f * mDensity, mSatValTrackerPaint);
+
+ mSatValTrackerPaint.setColor(0xffdddddd);
+ canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS, mSatValTrackerPaint);
+
+ }
+
+ private void drawHuePanel(Canvas canvas){
+
+ final RectF rect = mHueRect;
+
+ if(BORDER_WIDTH_PX > 0){
+ mBorderPaint.setColor(mBorderColor);
+ canvas.drawRect(rect.left - BORDER_WIDTH_PX,
+ rect.top - BORDER_WIDTH_PX,
+ rect.right + BORDER_WIDTH_PX,
+ rect.bottom + BORDER_WIDTH_PX,
+ mBorderPaint);
+ }
+
+ if (mHueShader == null) {
+ mHueShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, buildHueColorArray(), null, TileMode.CLAMP);
+ mHuePaint.setShader(mHueShader);
+ }
+
+ canvas.drawRect(rect, mHuePaint);
+
+ float rectHeight = 4 * mDensity / 2;
+
+ Point p = hueToPoint(mHue);
+
+ RectF r = new RectF();
+ r.left = rect.left - RECTANGLE_TRACKER_OFFSET;
+ r.right = rect.right + RECTANGLE_TRACKER_OFFSET;
+ r.top = p.y - rectHeight;
+ r.bottom = p.y + rectHeight;
+
+
+ canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint);
+
+ }
+
+ private void drawAlphaPanel(Canvas canvas){
+
+ if(!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) return;
+
+ final RectF rect = mAlphaRect;
+
+ if(BORDER_WIDTH_PX > 0){
+ mBorderPaint.setColor(mBorderColor);
+ canvas.drawRect(rect.left - BORDER_WIDTH_PX,
+ rect.top - BORDER_WIDTH_PX,
+ rect.right + BORDER_WIDTH_PX,
+ rect.bottom + BORDER_WIDTH_PX,
+ mBorderPaint);
+ }
+
+
+ mAlphaPattern.draw(canvas);
+
+ float[] hsv = new float[]{mHue,mSat,mVal};
+ int color = Color.HSVToColor(hsv);
+ int acolor = Color.HSVToColor(0, hsv);
+
+ mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
+ color, acolor, TileMode.CLAMP);
+
+
+ mAlphaPaint.setShader(mAlphaShader);
+
+ canvas.drawRect(rect, mAlphaPaint);
+
+ if(mAlphaSliderText != null && mAlphaSliderText!= ""){
+ canvas.drawText(mAlphaSliderText, rect.centerX(), rect.centerY() + 4 * mDensity, mAlphaTextPaint);
+ }
+
+ float rectWidth = 4 * mDensity / 2;
+
+ Point p = alphaToPoint(mAlpha);
+
+ RectF r = new RectF();
+ r.left = p.x - rectWidth;
+ r.right = p.x + rectWidth;
+ r.top = rect.top - RECTANGLE_TRACKER_OFFSET;
+ r.bottom = rect.bottom + RECTANGLE_TRACKER_OFFSET;
+
+ canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint);
+
+ }
+
+
+ private Point hueToPoint(float hue){
+
+ final RectF rect = mHueRect;
+ final float height = rect.height();
+
+ Point p = new Point();
+
+ p.y = (int) (height - (hue * height / 360f) + rect.top);
+ p.x = (int) rect.left;
+
+ return p;
+ }
+
+ private Point satValToPoint(float sat, float val){
+
+ final RectF rect = mSatValRect;
+ final float height = rect.height();
+ final float width = rect.width();
+
+ Point p = new Point();
+
+ p.x = (int) (sat * width + rect.left);
+ p.y = (int) ((1f - val) * height + rect.top);
+
+ return p;
+ }
+
+ private Point alphaToPoint(int alpha){
+
+ final RectF rect = mAlphaRect;
+ final float width = rect.width();
+
+ Point p = new Point();
+
+ p.x = (int) (width - (alpha * width / 0xff) + rect.left);
+ p.y = (int) rect.top;
+
+ return p;
+
+ }
+
+ private float[] pointToSatVal(float x, float y){
+
+ final RectF rect = mSatValRect;
+ float[] result = new float[2];
+
+ float width = rect.width();
+ float height = rect.height();
+
+ if (x < rect.left){
+ x = 0f;
+ }
+ else if(x > rect.right){
+ x = width;
+ }
+ else{
+ x = x - rect.left;
+ }
+
+ if (y < rect.top){
+ y = 0f;
+ }
+ else if(y > rect.bottom){
+ y = height;
+ }
+ else{
+ y = y - rect.top;
+ }
+
+
+ result[0] = 1.f / width * x;
+ result[1] = 1.f - (1.f / height * y);
+
+ return result;
+ }
+
+ private float pointToHue(float y){
+
+ final RectF rect = mHueRect;
+
+ float height = rect.height();
+
+ if (y < rect.top){
+ y = 0f;
+ }
+ else if(y > rect.bottom){
+ y = height;
+ }
+ else{
+ y = y - rect.top;
+ }
+
+ return 360f - (y * 360f / height);
+ }
+
+ private int pointToAlpha(int x){
+
+ final RectF rect = mAlphaRect;
+ final int width = (int) rect.width();
+
+ if(x < rect.left){
+ x = 0;
+ }
+ else if(x > rect.right){
+ x = width;
+ }
+ else{
+ x = x - (int)rect.left;
+ }
+
+ return 0xff - (x * 0xff / width);
+
+ }
+
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+
+ float x = event.getX();
+ float y = event.getY();
+
+ boolean update = false;
+
+
+ if(event.getAction() == MotionEvent.ACTION_MOVE){
+
+ switch(mLastTouchedPanel){
+
+ case PANEL_SAT_VAL:
+
+ float sat, val;
+
+ sat = mSat + x/50f;
+ val = mVal - y/50f;
+
+ if(sat < 0f){
+ sat = 0f;
+ }
+ else if(sat > 1f){
+ sat = 1f;
+ }
+
+ if(val < 0f){
+ val = 0f;
+ }
+ else if(val > 1f){
+ val = 1f;
+ }
+
+ mSat = sat;
+ mVal = val;
+
+ update = true;
+
+ break;
+
+ case PANEL_HUE:
+
+ float hue = mHue - y * 10f;
+
+ if(hue < 0f){
+ hue = 0f;
+ }
+ else if(hue > 360f){
+ hue = 360f;
+ }
+
+ mHue = hue;
+
+ update = true;
+
+ break;
+
+ case PANEL_ALPHA:
+
+ if(!mShowAlphaPanel || mAlphaRect == null){
+ update = false;
+ }
+ else{
+
+ int alpha = (int) (mAlpha - x*10);
+
+ if(alpha < 0){
+ alpha = 0;
+ }
+ else if(alpha > 0xff){
+ alpha = 0xff;
+ }
+
+ mAlpha = alpha;
+
+
+ update = true;
+ }
+
+ break;
+ }
+
+
+ }
+
+
+ if(update){
+
+ if(mListener != null){
+ mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal}));
+ }
+
+ invalidate();
+ return true;
+ }
+
+
+ return super.onTrackballEvent(event);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+
+ boolean update = false;
+
+ switch(event.getAction()){
+
+ case MotionEvent.ACTION_DOWN:
+
+ mStartTouchPoint = new Point((int)event.getX(), (int)event.getY());
+
+ update = moveTrackersIfNeeded(event);
+
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+
+ update = moveTrackersIfNeeded(event);
+
+ break;
+
+ case MotionEvent.ACTION_UP:
+
+ mStartTouchPoint = null;
+
+ update = moveTrackersIfNeeded(event);
+
+ break;
+
+ }
+
+ if(update){
+
+ if(mListener != null){
+ mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal}));
+ }
+
+ invalidate();
+ return true;
+ }
+
+
+ return super.onTouchEvent(event);
+ }
+
+ private boolean moveTrackersIfNeeded(MotionEvent event){
+
+ if(mStartTouchPoint == null) return false;
+
+ boolean update = false;
+
+ int startX = mStartTouchPoint.x;
+ int startY = mStartTouchPoint.y;
+
+
+ if(mHueRect.contains(startX, startY)){
+ mLastTouchedPanel = PANEL_HUE;
+
+ mHue = pointToHue(event.getY());
+
+ update = true;
+ }
+ else if(mSatValRect.contains(startX, startY)){
+
+ mLastTouchedPanel = PANEL_SAT_VAL;
+
+ float[] result = pointToSatVal(event.getX(), event.getY());
+
+ mSat = result[0];
+ mVal = result[1];
+
+ update = true;
+ }
+ else if(mAlphaRect != null && mAlphaRect.contains(startX, startY)){
+
+ mLastTouchedPanel = PANEL_ALPHA;
+
+ mAlpha = pointToAlpha((int)event.getX());
+
+ update = true;
+ }
+
+
+ return update;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+ int width = 0;
+ int height = 0;
+
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ int widthAllowed = MeasureSpec.getSize(widthMeasureSpec);
+ int heightAllowed = MeasureSpec.getSize(heightMeasureSpec);
+
+ widthAllowed = chooseWidth(widthMode, widthAllowed);
+ heightAllowed = chooseHeight(heightMode, heightAllowed);
+
+ if(!mShowAlphaPanel){
+
+ height = (int) (widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH);
+
+ //If calculated height (based on the width) is more than the allowed height.
+ if(height > heightAllowed || getTag().equals("landscape")) {
+ height = heightAllowed;
+ width = (int) (height + PANEL_SPACING + HUE_PANEL_WIDTH);
+ }
+ else{
+ width = widthAllowed;
+ }
+ }
+ else{
+
+ width = (int) (heightAllowed - ALPHA_PANEL_HEIGHT + HUE_PANEL_WIDTH);
+
+ if(width > widthAllowed){
+ width = widthAllowed;
+ height = (int) (widthAllowed - HUE_PANEL_WIDTH + ALPHA_PANEL_HEIGHT);
+ }
+ else{
+ height = heightAllowed;
+ }
+
+ }
+
+ setMeasuredDimension(width, height);
+ }
+
+ private int chooseWidth(int mode, int size){
+ if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
+ return size;
+ } else { // (mode == MeasureSpec.UNSPECIFIED)
+ return getPrefferedWidth();
+ }
+ }
+
+ private int chooseHeight(int mode, int size){
+ if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
+ return size;
+ } else { // (mode == MeasureSpec.UNSPECIFIED)
+ return getPrefferedHeight();
+ }
+ }
+
+ private int getPrefferedWidth(){
+
+ int width = getPrefferedHeight();
+
+ if(mShowAlphaPanel){
+ width -= (PANEL_SPACING + ALPHA_PANEL_HEIGHT);
+ }
+
+
+ return (int) (width + HUE_PANEL_WIDTH + PANEL_SPACING);
+
+ }
+
+ private int getPrefferedHeight(){
+
+ int height = (int)(200 * mDensity);
+
+ if(mShowAlphaPanel){
+ height += PANEL_SPACING + ALPHA_PANEL_HEIGHT;
+ }
+
+ return height;
+ }
+
+
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ mDrawingRect = new RectF();
+ mDrawingRect.left = mDrawingOffset + getPaddingLeft();
+ mDrawingRect.right = w - mDrawingOffset - getPaddingRight();
+ mDrawingRect.top = mDrawingOffset + getPaddingTop();
+ mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom();
+
+ setUpSatValRect();
+ setUpHueRect();
+ setUpAlphaRect();
+ }
+
+ private void setUpSatValRect(){
+
+ final RectF dRect = mDrawingRect;
+ float panelSide = dRect.height() - BORDER_WIDTH_PX * 2;
+
+ if(mShowAlphaPanel){
+ panelSide -= PANEL_SPACING + ALPHA_PANEL_HEIGHT;
+ }
+
+ float left = dRect.left + BORDER_WIDTH_PX;
+ float top = dRect.top + BORDER_WIDTH_PX;
+ float bottom = top + panelSide;
+ float right = left + panelSide;
+
+ mSatValRect = new RectF(left,top, right, bottom);
+ }
+
+ private void setUpHueRect(){
+ final RectF dRect = mDrawingRect;
+
+ float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX;
+ float top = dRect.top + BORDER_WIDTH_PX;
+ float bottom = dRect.bottom - BORDER_WIDTH_PX - (mShowAlphaPanel ? (PANEL_SPACING + ALPHA_PANEL_HEIGHT) : 0);
+ float right = dRect.right - BORDER_WIDTH_PX;
+
+ mHueRect = new RectF(left, top, right, bottom);
+ }
+
+ private void setUpAlphaRect() {
+
+ if(!mShowAlphaPanel) return;
+
+ final RectF dRect = mDrawingRect;
+
+ float left = dRect.left + BORDER_WIDTH_PX;
+ float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX;
+ float bottom = dRect.bottom - BORDER_WIDTH_PX;
+ float right = dRect.right - BORDER_WIDTH_PX;
+
+ mAlphaRect = new RectF(left, top, right, bottom);
+
+ mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity));
+ mAlphaPattern.setBounds(
+ Math.round(mAlphaRect.left),
+ Math.round(mAlphaRect.top),
+ Math.round(mAlphaRect.right),
+ Math.round(mAlphaRect.bottom)
+ );
+
+ }
+
+
+ /**
+ * Set a OnColorChangedListener to get notified when the color
+ * selected by the user has changed.
+ * @param listener
+ */
+ public void setOnColorChangedListener(OnColorChangedListener listener){
+ mListener = listener;
+ }
+
+ /**
+ * Set the color of the border surrounding all panels.
+ * @param color
+ */
+ public void setBorderColor(int color){
+ mBorderColor = color;
+ invalidate();
+ }
+
+ /**
+ * Get the color of the border surrounding all panels.
+ */
+ public int getBorderColor(){
+ return mBorderColor;
+ }
+
+ /**
+ * Get the current color this view is showing.
+ * @return the current color.
+ */
+ public int getColor(){
+ return Color.HSVToColor(mAlpha, new float[]{mHue,mSat,mVal});
+ }
+
+ /**
+ * Set the color the view should show.
+ * @param color The color that should be selected.
+ */
+ public void setColor(int color){
+ setColor(color, false);
+ }
+
+ /**
+ * Set the color this view should show.
+ * @param color The color that should be selected.
+ * @param callback If you want to get a callback to
+ * your OnColorChangedListener.
+ */
+ public void setColor(int color, boolean callback){
+
+ int alpha = Color.alpha(color);
+ int red = Color.red(color);
+ int blue = Color.blue(color);
+ int green = Color.green(color);
+
+ float[] hsv = new float[3];
+
+ Color.RGBToHSV(red, green, blue, hsv);
+
+ mAlpha = alpha;
+ mHue = hsv[0];
+ mSat = hsv[1];
+ mVal = hsv[2];
+
+ if(callback && mListener != null){
+ mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal}));
+ }
+
+ invalidate();
+ }
+
+ /**
+ * Get the drawing offset of the color picker view.
+ * The drawing offset is the distance from the side of
+ * a panel to the side of the view minus the padding.
+ * Useful if you want to have your own panel below showing
+ * the currently selected color and want to align it perfectly.
+ * @return The offset in pixels.
+ */
+ public float getDrawingOffset(){
+ return mDrawingOffset;
+ }
+
+ /**
+ * Set if the user is allowed to adjust the alpha panel. Default is false.
+ * If it is set to false no alpha will be set.
+ * @param visible
+ */
+ public void setAlphaSliderVisible(boolean visible){
+
+ if(mShowAlphaPanel != visible){
+ mShowAlphaPanel = visible;
+
+ /*
+ * Reset all shader to force a recreation.
+ * Otherwise they will not look right after
+ * the size of the view has changed.
+ */
+ mValShader = null;
+ mSatShader = null;
+ mHueShader = null;
+ mAlphaShader = null;;
+
+ requestLayout();
+ }
+
+ }
+
+ public void setSliderTrackerColor(int color){
+ mSliderTrackerColor = color;
+
+ mHueTrackerPaint.setColor(mSliderTrackerColor);
+
+ invalidate();
+ }
+
+ public int getSliderTrackerColor(){
+ return mSliderTrackerColor;
+ }
+
+ /**
+ * Set the text that should be shown in the
+ * alpha slider. Set to null to disable text.
+ * @param res string resource id.
+ */
+ public void setAlphaSliderText(int res){
+ String text = getContext().getString(res);
+ setAlphaSliderText(text);
+ }
+
+ /**
+ * Set the text that should be shown in the
+ * alpha slider. Set to null to disable text.
+ * @param text Text that should be shown.
+ */
+ public void setAlphaSliderText(String text){
+ mAlphaSliderText = text;
+ invalidate();
+ }
+
+ /**
+ * Get the current value of the text
+ * that will be shown in the alpha
+ * slider.
+ * @return
+ */
+ public String getAlphaSliderText(){
+ return mAlphaSliderText;
+ }
+}
\ No newline at end of file
diff --git a/android/src/org/transdroid/gui/DetailsFragment.java b/android/src/org/transdroid/gui/DetailsFragment.java
index 8a89c84e..2e150034 100644
--- a/android/src/org/transdroid/gui/DetailsFragment.java
+++ b/android/src/org/transdroid/gui/DetailsFragment.java
@@ -529,6 +529,11 @@ public class DetailsFragment extends Fragment implements IDaemonCallback, OnSele
* } }
*/
+ @Override
+ public boolean isAttached() {
+ return getActivity() != null;
+ }
+
@Override
public void onQueueEmpty() {
// No active task: turn off status indicator
diff --git a/android/src/org/transdroid/gui/TaskResultHandler.java b/android/src/org/transdroid/gui/TaskResultHandler.java
index 4f676002..f8c55906 100644
--- a/android/src/org/transdroid/gui/TaskResultHandler.java
+++ b/android/src/org/transdroid/gui/TaskResultHandler.java
@@ -90,5 +90,10 @@ public class TaskResultHandler extends Handler implements IDaemonCallback {
msg.obj = result;
sendMessage(msg);
}
+
+ @Override
+ public boolean isAttached() {
+ return callback.isAttached();
+ }
}
diff --git a/android/src/org/transdroid/gui/TorrentsFragment.java b/android/src/org/transdroid/gui/TorrentsFragment.java
index f551bb59..4afa19c0 100644
--- a/android/src/org/transdroid/gui/TorrentsFragment.java
+++ b/android/src/org/transdroid/gui/TorrentsFragment.java
@@ -1593,6 +1593,11 @@ public class TorrentsFragment extends Fragment implements IDaemonCallback, OnTou
return "";
}
+ @Override
+ public boolean isAttached() {
+ return getActivity() != null;
+ }
+
@Override
public void onTaskFailure(DaemonTaskFailureResult result) {
@@ -1970,7 +1975,8 @@ public class TorrentsFragment extends Fragment implements IDaemonCallback, OnTou
private void setProgressBar(boolean b) {
inProgress = b;
- getSupportActivity().invalidateOptionsMenu();
+ if (getSupportActivity() != null)
+ getSupportActivity().invalidateOptionsMenu();
}
protected View findViewById(int id) {
diff --git a/android/src/org/transdroid/preferences/Preferences.java b/android/src/org/transdroid/preferences/Preferences.java
index b21adc85..a2f94a0e 100644
--- a/android/src/org/transdroid/preferences/Preferences.java
+++ b/android/src/org/transdroid/preferences/Preferences.java
@@ -127,12 +127,13 @@ public class Preferences {
public static final String KEY_PREF_ALARMPLAYSOUND = "transdroid_alarm_playsound";
public static final String KEY_PREF_ALARMSOUNDURI = "transdroid_alarm_sounduri";
public static final String KEY_PREF_ALARMVIBRATE = "transdroid_alarm_vibrate";
- public static final String KEY_PREF_ADWNOTIFY = "transdroid_alarm_adwnotify";
- public static final String KEY_PREF_ADWONLYDL = "transdroid_alarm_adwonlydl";
+ public static final String KEY_PREF_ALARMCOLOUR = "transdroid_alarm_colour";
+ public static final String KEY_PREF_ADWNOTIFY = "transdroid_alarm_adwnotify";
+ public static final String KEY_PREF_ADWONLYDL = "transdroid_alarm_adwonlydl";
- public static final String KEY_WIDGET_DAEMON = "transdroid_widget_daemon";
- public static final String KEY_WIDGET_REFRESH = "transdroid_widget_refresh";
- public static final String KEY_WIDGET_LAYOUT = "transdroid_widget_layout";
+ public static final String KEY_WIDGET_DAEMON = "transdroid_widget_daemon";
+ public static final String KEY_WIDGET_REFRESH = "transdroid_widget_refresh";
+ public static final String KEY_WIDGET_LAYOUT = "transdroid_widget_layout";
/**
* Determines the order number of the last used daemon settings object
@@ -918,6 +919,7 @@ public class Preferences {
prefs.getBoolean(KEY_PREF_ALARMPLAYSOUND, false),
prefs.getString(KEY_PREF_ALARMSOUNDURI, null),
prefs.getBoolean(KEY_PREF_ALARMVIBRATE, false),
+ prefs.getInt(KEY_PREF_ALARMCOLOUR, 0xff7dbb21),
prefs.getBoolean(KEY_PREF_ADWNOTIFY, false),
prefs.getBoolean(KEY_PREF_ADWONLYDL, false));
}
diff --git a/android/src/org/transdroid/preferences/PreferencesAlarm.java b/android/src/org/transdroid/preferences/PreferencesAlarm.java
index 3ed4826f..5429cafe 100644
--- a/android/src/org/transdroid/preferences/PreferencesAlarm.java
+++ b/android/src/org/transdroid/preferences/PreferencesAlarm.java
@@ -17,6 +17,8 @@
*/
package org.transdroid.preferences;
+import net.margaritov.preference.colorpicker.ColorPickerPreference;
+
import org.transdroid.R;
import org.transdroid.service.BootReceiver;
@@ -39,6 +41,7 @@ public class PreferencesAlarm extends PreferenceActivity {
private TransdroidCheckBoxPreference alarmPlaySound;
private TransdroidNotificationListPreference alarmSoundURI;
private TransdroidCheckBoxPreference alarmVibrate;
+ private ColorPickerPreference alarmColour;
private TransdroidCheckBoxPreference adwNotify;
private TransdroidCheckBoxPreference adwOnlyDl;
@@ -78,14 +81,14 @@ public class PreferencesAlarm extends PreferenceActivity {
checkRssFeeds.setKey(Preferences.KEY_PREF_CHECKRSSFEEDS);
checkRssFeeds.setEnabled(isEnabled);
getPreferenceScreen().addItemFromInflater(checkRssFeeds);
- // alarm play sound
+ // Alarm play sound
alarmPlaySound = new TransdroidCheckBoxPreference(this);
alarmPlaySound.setTitle(R.string.pref_alarmplaysound);
alarmPlaySound.setSummary(R.string.pref_alarmplaysound_info);
alarmPlaySound.setKey(Preferences.KEY_PREF_ALARMPLAYSOUND);
alarmPlaySound.setEnabled(isEnabled);
getPreferenceScreen().addItemFromInflater(alarmPlaySound);
- // alarm sound URI
+ // Alarm sound URI
alarmSoundURI = new TransdroidNotificationListPreference(this);
alarmSoundURI.setTitle(R.string.pref_alarmsounduri);
alarmSoundURI.setSummary(R.string.pref_alarmsounduri_info);
@@ -95,13 +98,22 @@ public class PreferencesAlarm extends PreferenceActivity {
alarmSoundURI.setShowSilent(false);
alarmSoundURI.setEnabled(isEnabled);
getPreferenceScreen().addItemFromInflater(alarmSoundURI);
- // vibrate
+ // Vibrate
alarmVibrate = new TransdroidCheckBoxPreference(this);
alarmVibrate.setTitle(R.string.pref_alarmvibrate);
alarmVibrate.setSummary(R.string.pref_alarmvibrate_info);
alarmVibrate.setKey(Preferences.KEY_PREF_ALARMVIBRATE);
alarmVibrate.setEnabled(isEnabled);
getPreferenceScreen().addItemFromInflater(alarmVibrate);
+ // Notification LED colour
+ alarmColour = new ColorPickerPreference(this);
+ alarmColour.setTitle(R.string.pref_alarmcolour);
+ alarmColour.setSummary(R.string.pref_alarmcolour_info);
+ alarmColour.setKey(Preferences.KEY_PREF_ALARMCOLOUR);
+ alarmColour.setAlphaSliderEnabled(false);
+ alarmColour.setDefaultValue(0xff7dbb21);
+ alarmColour.setEnabled(isEnabled);
+ getPreferenceScreen().addItemFromInflater(alarmColour);
// Enable ADW notifications
adwNotify = new TransdroidCheckBoxPreference(this);
adwNotify.setTitle(R.string.pref_adwnotify);
@@ -153,6 +165,7 @@ public class PreferencesAlarm extends PreferenceActivity {
alarmPlaySound.setEnabled(isEnabled);
alarmSoundURI.setEnabled(isEnabled);
alarmVibrate.setEnabled(isEnabled);
+ alarmColour.setEnabled(isEnabled);
adwNotify.setEnabled(isEnabled);
adwOnlyDl.setEnabled(isEnabled && isAdwEnabled);
}
diff --git a/android/src/org/transdroid/service/AlarmService.java b/android/src/org/transdroid/service/AlarmService.java
index eba9daf4..47454235 100644
--- a/android/src/org/transdroid/service/AlarmService.java
+++ b/android/src/org/transdroid/service/AlarmService.java
@@ -314,15 +314,22 @@ public class AlarmService extends IntentService {
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
- //if sound enabled add to notification
+ // If sound enabled add to notification
if (settings.getAlarmPlaySound() && settings.getAlarmSoundURI() != null) {
newNotification.sound = Uri.parse(settings.getAlarmSoundURI());
}
- //if vibration enabled add to notification
+ // If vibration enabled add to notification
if (settings.getAlarmVibrate()) {
newNotification.defaults = Notification.DEFAULT_VIBRATE;
}
+
+ // Add coloured light; defaults to 0xff7dbb21
+ newNotification.ledARGB = settings.getAlarmColour();
+ newNotification.ledOnMS = 600;
+ newNotification.ledOffMS = 1000;
+ newNotification.flags |= Notification.FLAG_SHOW_LIGHTS;
+
// Send notification
notificationManager.notify(notifyID, newNotification);
diff --git a/android/src/org/transdroid/service/AlarmSettings.java b/android/src/org/transdroid/service/AlarmSettings.java
index 3357ebd7..a10059a0 100644
--- a/android/src/org/transdroid/service/AlarmSettings.java
+++ b/android/src/org/transdroid/service/AlarmSettings.java
@@ -31,16 +31,18 @@ public class AlarmSettings{
private boolean alarmPlaySound;
private String alarmSoundURI;
private boolean alarmVibrate;
+ private int alarmColour;
private boolean adwNotify;
private boolean adwOnlyDl;
- public AlarmSettings(boolean enableAlarm, int alarmInterval, boolean checkRssFeeds, boolean alarmPlaySound, String alarmSoundURI, boolean alarmVibrate, boolean adwNotify, boolean adwOnlyDl) {
+ public AlarmSettings(boolean enableAlarm, int alarmInterval, boolean checkRssFeeds, boolean alarmPlaySound, String alarmSoundURI, boolean alarmVibrate, int alarmColour, boolean adwNotify, boolean adwOnlyDl) {
this.enableAlarm = enableAlarm;
this.alarmInterval = alarmInterval;
this.checkRssFeeds = checkRssFeeds;
this.alarmPlaySound = alarmPlaySound;
this.alarmSoundURI = alarmSoundURI;
this.alarmVibrate = alarmVibrate;
+ this.alarmColour = alarmColour;
this.adwNotify = adwNotify;
this.adwOnlyDl = adwOnlyDl;
}
@@ -68,11 +70,15 @@ public class AlarmSettings{
public String getAlarmSoundURI() {
return alarmSoundURI;
}
-
+
public boolean getAlarmVibrate() {
return alarmVibrate;
}
+ public int getAlarmColour() {
+ return alarmColour;
+ }
+
public boolean showAdwNotifications() {
return adwNotify;
}
diff --git a/lib/.classpath b/lib/.classpath
index e7acd261..f2d4ba8a 100644
--- a/lib/.classpath
+++ b/lib/.classpath
@@ -2,6 +2,6 @@
-
+
diff --git a/lib/src/org/transdroid/daemon/IDaemonCallback.java b/lib/src/org/transdroid/daemon/IDaemonCallback.java
index e57de8e6..8c76f967 100644
--- a/lib/src/org/transdroid/daemon/IDaemonCallback.java
+++ b/lib/src/org/transdroid/daemon/IDaemonCallback.java
@@ -38,5 +38,7 @@ public interface IDaemonCallback {
void onTaskFailure(DaemonTaskFailureResult result);
void onTaskSuccess(DaemonTaskSuccessResult result);
+
+ boolean isAttached();
}
diff --git a/lib/src/org/transdroid/daemon/TaskQueue.java b/lib/src/org/transdroid/daemon/TaskQueue.java
index 602252c8..dd16189d 100644
--- a/lib/src/org/transdroid/daemon/TaskQueue.java
+++ b/lib/src/org/transdroid/daemon/TaskQueue.java
@@ -156,17 +156,19 @@ public class TaskQueue implements Runnable {
return;
}
- callback.onQueuedTaskStarted(task);
+ if (callback.isAttached())
+ callback.onQueuedTaskStarted(task);
// Ask the daemon adapter to perform the task (which does it synchronously)
DLog.d(LOG_NAME, "Starting task: " + task.toString());
DaemonTaskResult result = task.execute();
- callback.onQueuedTaskFinished(task);
+ if (callback.isAttached())
+ callback.onQueuedTaskFinished(task);
// Return the result (to the UI thread)
DLog.d(LOG_NAME, "Task result: " + (result == null? "null": result.toString()));
- if (result != null && !this.paused) {
+ if (result != null && !this.paused && callback.isAttached()) {
if (result.wasSuccessful()) {
callback.onTaskSuccess((DaemonTaskSuccessResult) result);
} else {