extracting common classes to another project

This commit is contained in:
Sergey Solovyev
2012-01-15 01:50:37 +04:00
parent 461126bee8
commit c46872b978
96 changed files with 22 additions and 5458 deletions

View File

@@ -1,207 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TabHost;
import android.widget.TextView;
import com.google.ads.AdRequest;
import com.google.ads.AdSize;
import com.google.ads.AdView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* User: serso
* Date: 12/21/11
* Time: 11:54 PM
*/
public final class AndroidUtils {
// not intended for instantiation
private AndroidUtils() {
throw new AssertionError();
}
public static void centerAndWrapTabsFor(@NotNull TabHost tabHost) {
if (allowCenterAndWrappingTabs()) {
int tabCount = tabHost.getTabWidget().getTabCount();
for (int i = 0; i < tabCount; i++) {
final View view = tabHost.getTabWidget().getChildTabViewAt(i);
if (view != null) {
if (view.getLayoutParams().height > 0) {
// reduce height of the tab
view.getLayoutParams().height *= 0.8;
}
// get title text view
final View textView = view.findViewById(android.R.id.title);
if (textView instanceof TextView) {
// just in case check the type
// center text
((TextView) textView).setGravity(Gravity.CENTER);
// wrap text
((TextView) textView).setSingleLine(false);
// explicitly set layout parameters
textView.getLayoutParams().height = ViewGroup.LayoutParams.FILL_PARENT;
textView.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
}
}
}
}
}
private static boolean allowCenterAndWrappingTabs() {
boolean result = true;
String deviceModel = Build.MODEL;
if (deviceModel != null) {
deviceModel = deviceModel.toUpperCase();
if (deviceModel.contains("M1") || deviceModel.contains("MIONE") || deviceModel.contains("MI-ONE")) {
// Xiaomi Phone MiOne => do not allow to center and wrap tabs
result = false;
Log.i(AndroidUtils.class.getName(), "Device model doesn't support center and wrap of tabs: " + Build.MODEL);
}
}
if (result) {
String buildId = Build.DISPLAY;
if (buildId != null) {
buildId = buildId.toUpperCase();
if (buildId.contains("MIUI")) {
// fix for MIUI ROM
result = false;
Log.i(AndroidUtils.class.getName(), "Device build doesn't support center and wrap of tabs: " + Build.DISPLAY);
}
}
}
return result;
}
public static void addTab(@NotNull Context context,
@NotNull TabHost tabHost,
@NotNull String tabId,
int tabCaptionId,
@NotNull Class<? extends Activity> activityClass) {
TabHost.TabSpec spec;
final Intent intent = new Intent().setClass(context, activityClass);
// Initialize a TabSpec for each tab and add it to the TabHost
spec = tabHost.newTabSpec(tabId).setIndicator(context.getString(tabCaptionId)).setContent(intent);
tabHost.addTab(spec);
}
/**
* @param context context
* @param appPackageName - full name of the package of an app, 'com.example.app' for example.
* @return version number we are currently in
*/
public static int getAppVersionCode(@NotNull Context context, @NotNull String appPackageName) {
try {
return context.getPackageManager().getPackageInfo(appPackageName, 0).versionCode;
} catch (PackageManager.NameNotFoundException e) {
// App not installed!
}
return -1;
}
@NotNull
public static AdView createAndInflateAdView(@NotNull Activity activity,
@NotNull String admobAccountId,
@Nullable ViewGroup parentView,
int layoutId,
@NotNull List<String> keywords) {
final ViewGroup layout = parentView != null ? parentView : (ViewGroup) activity.findViewById(layoutId);
// Create the adView
final AdView adView = new AdView(activity, AdSize.BANNER, admobAccountId);
// Add the adView to it
layout.addView(adView);
// Initiate a generic request to load it with an ad
final AdRequest adRequest = new AdRequest();
// todo serso: revert - only for tests
//adRequest.addTestDevice(AdRequest.TEST_EMULATOR);
//adRequest.addTestDevice("DB3C2F605A1296971898F0E60224A927");
for (String keyword : keywords) {
adRequest.addKeyword(keyword);
}
adView.loadAd(adRequest);
return adView;
}
public static <T> void processViewsOfType(@NotNull View view, @NotNull Class<T> clazz, @NotNull ViewProcessor<T> viewProcessor) {
processViewsOfType0(view, clazz, viewProcessor);
}
public static void processViews(@NotNull View view, @NotNull ViewProcessor<View> viewProcessor) {
processViewsOfType0(view, null, viewProcessor);
}
private static <T> void processViewsOfType0(@NotNull View view, @Nullable Class<T> clazz, @NotNull ViewProcessor<T> viewProcessor) {
if (view instanceof ViewGroup) {
final ViewGroup viewGroup = (ViewGroup) view;
if (clazz == null || ViewGroup.class.isAssignableFrom(clazz)) {
//noinspection unchecked
viewProcessor.process((T) viewGroup);
}
for (int index = 0; index < viewGroup.getChildCount(); index++) {
processViewsOfType0(viewGroup.getChildAt(index), clazz, viewProcessor);
}
} else if (clazz == null || view.getClass().isAssignableFrom(clazz)) {
//noinspection unchecked
viewProcessor.process((T) view);
}
}
public static interface ViewProcessor<V> {
void process(@NotNull V view);
}
public static void restartActivity(@NotNull Activity activity) {
final Intent intent = activity.getIntent();
/*
for compatibility with android_1.6_compatibility
overridePendingTransition(0, 0);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);*/
Log.d(activity.getClass().getName(), "Finishing current activity!");
activity.finish();
/*
for compatibility with android_1.6_compatibility
overridePendingTransition(0, 0);*/
Log.d(activity.getClass().getName(), "Starting new activity!");
activity.startActivity(intent);
}
}

View File

@@ -1,233 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.view.widgets.DragButton;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* User: serso
* Date: 11/25/11
* Time: 1:52 PM
*/
public enum ResourceCache {
instance;
@NotNull
private static final Locale DEFAULT_LOCALE = Locale.ENGLISH;
// ids of drag buttons in R.class
private List<Integer> dragButtonIds = null;
// ids of buttons in R.class
private List<Integer> buttonIds = null;
// first map: key: language id, value: map of captions and translations
// second mal: key: caption id, value: translation
private final Map<String, Map<String, String>> captions = new HashMap<String, Map<String, String>>();
private Class<?> resourceClass;
private Context context;
@NotNull
private final NameToIdCache nameToIdCache = new NameToIdCache();
public List<Integer> getDragButtonIds() {
return dragButtonIds;
}
public List<Integer> getButtonIds() {
return buttonIds;
}
/**
* Method load captions for default locale using android R class
*
* @param context STATIC CONTEXT
* @param resourceClass class of captions in android (SUBCLASS of R class)
*/
public void initCaptions(@NotNull Context context, @NotNull Class<?> resourceClass) {
initCaptions(context, resourceClass, Locale.getDefault());
}
/**
* Method load captions for specified locale using android R class
*
* @param context STATIC CONTEXT
* @param resourceClass class of captions in android (SUBCLASS of R class)
* @param locale language to be used for translation
*/
public void initCaptions(@NotNull Context context, @NotNull Class<?> resourceClass, @NotNull Locale locale) {
assert this.resourceClass == null || this.resourceClass.equals(resourceClass);
this.context = context;
this.resourceClass = resourceClass;
if (!initialized(locale)) {
final Map<String, String> captionsByLanguage = new HashMap<String, String>();
final Locale defaultLocale = Locale.getDefault();
try {
if (!defaultLocale.getLanguage().equals(locale.getLanguage())) {
updateConfiguration(context, locale);
}
for (Field field : resourceClass.getDeclaredFields()) {
int modifiers = field.getModifiers();
if (Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)) {
try {
int captionId = field.getInt(resourceClass);
captionsByLanguage.put(field.getName(), context.getString(captionId));
} catch (IllegalAccessException e) {
Log.e(ResourceCache.class.getName(), e.getMessage());
} catch (Resources.NotFoundException e) {
Log.e(ResourceCache.class.getName(), "Caption with name " + field.getName() + " was not found for " + locale.getLanguage() + " language: " + e.getMessage());
}
}
}
} finally {
if (!defaultLocale.getLanguage().equals(locale.getLanguage())) {
updateConfiguration(context, defaultLocale);
}
}
captions.put(locale.getLanguage(), captionsByLanguage);
}
}
private static void updateConfiguration(@NotNull Context context, @NotNull Locale locale) {
Locale.setDefault(locale);
final Configuration config = new Configuration();
config.locale = locale;
context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
}
private boolean initialized(@NotNull Locale locale) {
return captions.containsKey(locale.getLanguage());
}
/**
* @param captionId id of caption to be translated
* @return translation by caption id in default language, null if no translation in default language present
*/
@Nullable
public String getCaption(@NotNull String captionId) {
return getCaption(captionId, Locale.getDefault());
}
/**
* @param captionId id of caption to be translated
* @param locale language to be used for translation
* @return translation by caption id in specified language, null if no translation in specified language present
*/
@Nullable
public String getCaption(@NotNull String captionId, @NotNull final Locale locale) {
Map<String, String> captionsByLanguage = captions.get(locale.getLanguage());
if (captionsByLanguage != null) {
return getCaption(captionsByLanguage, captionId, locale);
} else {
assert resourceClass != null && context != null;
initCaptions(context, resourceClass, locale);
captionsByLanguage = captions.get(locale.getLanguage());
if (captionsByLanguage != null) {
return getCaption(captionsByLanguage, captionId, locale);
}
}
return null;
}
@Nullable
private String getCaption(@NotNull Map<String, String> captionsByLanguage, @NotNull String captionId, @NotNull Locale locale) {
String result = captionsByLanguage.get(captionId);
if (result == null && !locale.getLanguage().equals(DEFAULT_LOCALE.getLanguage())) {
result = getCaption(captionId, DEFAULT_LOCALE);
}
return result;
}
public void init(@NotNull Class<?> resourceClass, @NotNull Activity activity) {
dragButtonIds = new ArrayList<Integer>();
buttonIds = new ArrayList<Integer>();
for (Field field : resourceClass.getDeclaredFields()) {
int modifiers = field.getModifiers();
if (Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)) {
try {
int viewId = field.getInt(resourceClass);
final View view = activity.findViewById(viewId);
if (view instanceof DragButton) {
dragButtonIds.add(viewId);
}
if (view instanceof Button) {
buttonIds.add(viewId);
}
} catch (IllegalAccessException e) {
Log.e(ResourceCache.class.getName(), e.getMessage());
}
}
}
}
@NotNull
public Map<String, Integer> getNameToIdCache(@NotNull Class<?> clazz) {
return this.nameToIdCache.getCache(clazz);
}
private static class NameToIdCache {
@NotNull
private final Map<Class<?>, Map<String, Integer>> caches = new HashMap<Class<?>, Map<String, Integer>>(3);
// not intended for instantiation
private NameToIdCache() {
}
@NotNull
public Map<String, Integer> getCache(@NotNull Class<?> clazz) {
Map<String, Integer> result = caches.get(clazz);
if (result == null) {
result = new HashMap<String, Integer>();
for (Field field : clazz.getDeclaredFields()) {
int modifiers = field.getModifiers();
if (Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)) {
try {
result.put(field.getName(), field.getInt(clazz));
} catch (IllegalAccessException e) {
Log.e(ResourceCache.class.getName(), e.getMessage());
}
}
}
caches.put(clazz, result);
}
return result;
}
}
}

View File

@@ -1,17 +1,8 @@
package org.solovyev.android.calculator;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.ViewGroup;
import com.google.ads.AdView;
import net.robotmedia.billing.BillingController;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.AndroidUtils;
import java.util.Collections;
import java.util.List;
import org.solovyev.android.ads.AdsController;
/**
* User: serso
@@ -37,53 +28,11 @@ public class CalculatorApplication extends android.app.Application {
return instance;
}
private static boolean isAdFreePurchased(@NotNull Context context) {
return BillingController.isPurchased(context.getApplicationContext(), AD_FREE_PRODUCT_ID);
}
private static boolean transactionsRestored = false;
public static boolean isAdFree(@NotNull Context context) {
// check if user already bought this product
boolean purchased = isAdFreePurchased(context);
if (!purchased && !transactionsRestored) {
// we must to restore all transactions done by user to guarantee that product was purchased or not
BillingController.restoreTransactions(context);
transactionsRestored = true;
// todo serso: may be call net.robotmedia.billing.BillingController.restoreTransactions() always before first check and get rid of second check
// check the billing one more time
purchased = isAdFreePurchased(context);
}
return purchased;
}
@Nullable
public static AdView inflateAd(@NotNull Activity activity) {
return inflateAd(activity, null, R.id.ad_parent_view);
}
@Nullable
public static AdView inflateAd(@NotNull Activity activity, @Nullable ViewGroup parentView, int parentViewId) {
AdView result = null;
if ( !isAdFree(activity) ) {
Log.d(activity.getClass().getName(), "Application is not ad free - inflating ad!");
final List<String> keywords = Collections.emptyList();
result = AndroidUtils.createAndInflateAdView(activity, ADMOB_USER_ID, parentView, parentViewId, keywords);
} else {
Log.d(activity.getClass().getName(), "Application is ad free - no ads!");
}
return result;
}
@Override
public void onCreate() {
super.onCreate();
//BillingController.setDebug(true);
BillingController.setConfiguration(new BillingController.IConfiguration() {
AdsController.getInstance().init(ADMOB_USER_ID, AD_FREE_PRODUCT_ID, new BillingController.IConfiguration() {
@Override
public byte[] getObfuscationSalt() {

View File

@@ -19,7 +19,6 @@ import org.solovyev.android.calculator.jscl.JsclOperation;
import org.solovyev.android.calculator.model.CalculatorEngine;
import org.solovyev.android.calculator.model.CalculatorParseException;
import org.solovyev.android.calculator.model.TextProcessor;
import org.solovyev.android.view.AMenuBuilder;
import org.solovyev.android.view.AMenuItem;
import org.solovyev.android.view.AutoResizeTextView;

View File

@@ -18,6 +18,7 @@ import net.robotmedia.billing.BillingRequest;
import net.robotmedia.billing.IBillingObserver;
import net.robotmedia.billing.model.Transaction;
import org.solovyev.android.AndroidUtils;
import org.solovyev.android.ads.AdsController;
import org.solovyev.android.calculator.model.CalculatorEngine;
import org.solovyev.android.view.widgets.VibratorContainer;
@@ -63,7 +64,7 @@ public class CalculatorPreferencesActivity extends PreferenceActivity implements
private void setAdFreeAction() {
final Preference adFreePreference = findPreference(CalculatorApplication.AD_FREE_P_KEY);
if (!CalculatorApplication.isAdFree(this)) {
if (!AdsController.getInstance().isAdFree(this)) {
Log.d(CalculatorPreferencesActivity.class.getName(), "Ad free is not purchased - enable preference!");
adFreePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@@ -76,7 +77,7 @@ public class CalculatorPreferencesActivity extends PreferenceActivity implements
new AlertDialog.Builder(CalculatorPreferencesActivity.this).setTitle(R.string.c_error).setMessage(R.string.c_billing_error).create().show();
} else {
Log.d(CalculatorPreferencesActivity.class.getName(), "Billing is supported - continue!");
if (!CalculatorApplication.isAdFree(CalculatorPreferencesActivity.this)) {
if (!AdsController.getInstance().isAdFree(CalculatorPreferencesActivity.this)) {
Log.d(CalculatorPreferencesActivity.class.getName(), "Item not purchased - try to purchase!");
// not purchased => purchasing

View File

@@ -20,6 +20,16 @@ public final class CalculatorSecurity {
@NotNull
public static String getPK() {
return "org.solovyev.android.calculator";
final StringBuilder result = new StringBuilder();
result.append("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A");
result.append("MIIBCgKCAQEAquP2a7dEhTaJEQeXtSyreH5dCmTDOd");
result.append("dElCfg0ijOeB8JTxBiJTXLWnLA0kMaT/sRXswUaYI61YCQOoik82");
result.append("qrFH7W4+OFtiLb8WGX+YPEpQQ/IBZu9qm3xzS9Nolu79EBff0/CLa1FuT9RtjO");
result.append("iTW8Q0VP9meQdJEkfqJEyVCgHain+MGoQaRXI45EzkYmkz8TBx6X6aJF5NBAXnAWeyD0wPX1");
result.append("uedHH7+LgLcjnPVw82YjyJSzYnaaD2GX0Y7PGoFe6J5K4yJGGX5mih45pe2HWcG5lAkQhu1uX2hCcCBdF3");
result.append("W7paRq9mJvCsbn+BNTh9gq8QKui0ltmiWpa5U+/9L+FQIDAQAB");
return result.toString();
}
}

View File

@@ -10,7 +10,7 @@ import android.app.Activity;
import android.os.Bundle;
import com.google.ads.AdView;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.CalculatorApplication;
import org.solovyev.android.ads.AdsController;
/**
* User: serso
@@ -35,7 +35,7 @@ public class AbstractHelpActivity extends Activity {
setContentView(layoutId);
// do not inflate ad in help (as some problems were encountered dut to ScrollView - no space for ad banner)
adView = CalculatorApplication.inflateAd(this);
adView = AdsController.getInstance().inflateAd(this);
}
@Override

View File

@@ -19,7 +19,7 @@ import android.widget.ListView;
import com.google.ads.AdView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.CalculatorApplication;
import org.solovyev.android.ads.AdsController;
import org.solovyev.android.calculator.CalculatorModel;
import org.solovyev.android.calculator.R;
import org.solovyev.android.calculator.jscl.JsclOperation;
@@ -67,7 +67,7 @@ public abstract class AbstractHistoryActivity extends ListActivity {
setContentView(R.layout.history_activity);
adView = CalculatorApplication.inflateAd(this);
adView = AdsController.getInstance().inflateAd(this);
adapter = new HistoryArrayAdapter(this, getLayoutId(), R.id.history_item, new ArrayList<CalculatorHistoryState>());
setListAdapter(adapter);

View File

@@ -18,7 +18,7 @@ import android.widget.*;
import com.google.ads.AdView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.CalculatorApplication;
import org.solovyev.android.ads.AdsController;
import org.solovyev.android.calculator.CalculatorModel;
import org.solovyev.android.calculator.R;
import org.solovyev.android.calculator.model.AndroidMathRegistry;
@@ -90,7 +90,7 @@ public abstract class AbstractMathEntityListActivity<T extends MathEntity> exten
setContentView(getLayoutId());
adView = CalculatorApplication.inflateAd(this);
adView = AdsController.getInstance().inflateAd(this);
final Intent intent = getIntent();
if ( intent != null ) {

View File

@@ -1,40 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.msg;
import org.jetbrains.annotations.NotNull;
import org.solovyev.android.ResourceCache;
import org.solovyev.common.msg.AbstractMessage;
import org.solovyev.common.msg.MessageType;
import java.util.List;
import java.util.Locale;
/**
* User: serso
* Date: 10/18/11
* Time: 11:57 PM
*/
public class AndroidMessage extends AbstractMessage {
public AndroidMessage(@NotNull String messageCode,
@NotNull MessageType messageType,
@org.jetbrains.annotations.Nullable Object... arguments) {
super(messageCode, messageType, arguments);
}
public AndroidMessage(@NotNull String messageCode,
@NotNull MessageType messageType,
@NotNull List<?> arguments) {
super(messageCode, messageType, arguments);
}
@Override
protected String getMessagePattern(@NotNull Locale locale) {
return ResourceCache.instance.getCaption(getMessageCode());
}
}

View File

@@ -1,24 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view;
import android.content.Context;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* User: serso
* Date: 12/18/11
* Time: 1:30 PM
*/
public interface AMenu<T extends AMenuItem<D>, D> {
@Nullable
T itemAt(int i);
@NotNull
CharSequence[] getMenuCaptions(@NotNull final Context context);
}

View File

@@ -1,60 +0,0 @@
package org.solovyev.android.view;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import org.jetbrains.annotations.NotNull;
/**
* User: serso
* Date: 12/19/11
* Time: 10:54 AM
*/
public class AMenuBuilder<T extends AMenuItem<D>, D>{
@NotNull
private final Context context;
@NotNull
private final AlertDialog.Builder menuBuilder;
@NotNull
private final AMenu<T, D> menu;
@NotNull
public static <T extends Enum & AMenuItem<D>, D> AMenuBuilder<T, D> newInstance(@NotNull Context context, @NotNull Class<T> enumClass) {
return new AMenuBuilder<T, D>(context, EnumMenu.<T, D>newInstance(enumClass));
}
@NotNull
public static <T extends AMenuItem<D>, D> AMenuBuilder<T, D> newInstance(@NotNull Context context, @NotNull AMenu<T, D> menu) {
return new AMenuBuilder<T, D>(context, menu);
}
private AMenuBuilder(@NotNull Context context, @NotNull AMenu<T, D> menu) {
this.context = context;
this.menuBuilder = new AlertDialog.Builder(context);
this.menu = menu;
}
@NotNull
public AlertDialog.Builder getMenuBuilder() {
return menuBuilder;
}
@NotNull
public AlertDialog create(@NotNull final D data) {
menuBuilder.setItems(menu.getMenuCaptions(context), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
final AMenuItem<D> menuItem = menu.itemAt(item);
if (menuItem != null) {
menuItem.doAction(data, context);
}
}
});
return menuBuilder.create();
}
}

View File

@@ -1,23 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view;
import android.content.Context;
import org.jetbrains.annotations.NotNull;
/**
* User: serso
* Date: 12/18/11
* Time: 1:29 PM
*/
public interface AMenuItem<T> {
@NotNull
String getCaption(@NotNull Context context);
void doAction(@NotNull T data, @NotNull Context context);
}

View File

@@ -1,301 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view;
import android.content.Context;
import android.graphics.Canvas;
import android.text.Editable;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;
/**
* Text view that auto adjusts text size to fit within the view.
* If the text size equals the minimum text size and still does not
* fit, append with an ellipsis.
*
* @author Chase Colburn
* @since Apr 4, 2011
*/
public class AutoResizeTextView extends TextView {
// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 20;
private float initialTextSize = 100;
// Interface for resize notifications
public interface OnTextResizeListener {
public void onTextResize(TextView textView, float oldSize, float newSize);
}
// Off screen canvas for text size rendering
private static final Canvas textResizeCanvas = new Canvas();
// Our ellipse string
private static final String ellipsis = "...";
// Registered resize listener
private OnTextResizeListener textResizeListener;
// Flag for text and/or size changes to force a resize
private boolean needsResize = false;
// Lower bounds for text size
private float minTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplier
private float spacingMult = 1.0f;
// Text view additional line spacing
private float spacingAdd = 0.0f;
// Add ellipsis to text that overflows at the smallest text size
private boolean addEllipsis = true;
// Default constructor override
public AutoResizeTextView(Context context) {
this(context, null);
}
// Default constructor when inflating from XML file
public AutoResizeTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
// Default constructor override
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* When text changes, set the force resize flag to true and resetInterpreter the text size.
*/
@Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
needsResize = true;
// Since this view may be reused, it is good to resetInterpreter the text size
}
/**
* If the text view size changed, set the force resize flag to true
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
needsResize = true;
}
}
/**
* Register listener to receive resize notifications
*
* @param listener
*/
public void setOnResizeListener(OnTextResizeListener listener) {
textResizeListener = listener;
}
/**
* Override the set text size to update our internal reference values
*/
@Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
}
/**
* Override the set line spacing to update our internal reference values
*/
@Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
spacingMult = mult;
spacingAdd = add;
}
/**
* Set the lower text size limit and invalidate the view
*
* @param minTextSize
*/
public void setMinTextSize(float minTextSize) {
this.minTextSize = minTextSize;
requestLayout();
invalidate();
}
/**
* Return lower text size limit
*
* @return
*/
public float getMinTextSize() {
return minTextSize;
}
/**
* Set flag to add ellipsis to text that overflows at the smallest text size
*
* @param addEllipsis
*/
public void setAddEllipsis(boolean addEllipsis) {
this.addEllipsis = addEllipsis;
}
/**
* Return flag to add ellipsis to text that overflows at the smallest text size
*
* @return
*/
public boolean getAddEllipsis() {
return addEllipsis;
}
/**
* Resize text after measuring
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed || needsResize) {
int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
resizeText(widthLimit, heightLimit, getText());
}
super.onLayout(changed, left, top, right, bottom);
}
/**
* Resize the text size with default width and height
*/
public void resizeText() {
resizeText(getText());
}
private void resizeText(final CharSequence text) {
int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
resizeText(widthLimit, heightLimit, text);
}
/**
* Resize the text size with specified width and height
*
* @param width
* @param height
* @param text
*/
private void resizeText(int width, int height, CharSequence text) {
Log.d(this.getClass().getName(), "Resizing: w=" + width + ", h=" + height + ", text='" + text + "'");
// Do not resize if the view does not have dimensions or there is no text
if (text == null || text.length() == 0 || height <= 0 || width <= 0) {
return;
}
// Get the text view's paint object
TextPaint textPaint = getPaint();
// Store the current text size
float oldTextSize = textPaint.getTextSize();
Log.d(this.getClass().getName(), "Old text size: " + oldTextSize);
// If there is a max text size set, use the lesser of that and the default text size
// todo serso: +2 is a workaround => to be checked boundary constraints
float newTextSize = initialTextSize + 2;
int newTextHeight;
if (text instanceof Editable) {
((Editable) text).append("|");
}
try {
// Get the required text height
newTextHeight = getTextRect(text, textPaint, width, newTextSize);
logDimensions(newTextSize, newTextHeight);
if (newTextHeight > height) {
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while (newTextHeight > height) {
if (newTextSize <= minTextSize) {
break;
}
newTextSize = Math.max(newTextSize - 1, minTextSize);
newTextHeight = getTextRect(text, textPaint, width, newTextSize);
logDimensions(newTextSize, newTextHeight);
}
} else {
while (newTextHeight < height) {
if (newTextSize <= minTextSize) {
break;
}
newTextSize = Math.max(newTextSize + 1, minTextSize);
newTextHeight = getTextRect(text, textPaint, width, newTextSize);
logDimensions(newTextSize, newTextHeight);
}
}
} finally {
if (text instanceof Editable) {
((Editable) text).delete(text.length() - 1, text.length());
}
}
initialTextSize = newTextSize;
// If we had reached our minimum text size and still don't fit, append an ellipsis
if (addEllipsis && newTextSize == minTextSize && newTextHeight > height) {
// Draw using a static layout
StaticLayout layout = new StaticLayout(text, textPaint, width, Alignment.ALIGN_NORMAL, spacingMult, spacingAdd, false);
layout.draw(textResizeCanvas);
int lastLine = layout.getLineForVertical(height) - 1;
int start = layout.getLineStart(lastLine);
int end = layout.getLineEnd(lastLine);
float lineWidth = layout.getLineWidth(lastLine);
float ellipseWidth = textPaint.measureText(ellipsis);
// Trim characters off until we have enough room to draw the ellipsis
while (width < lineWidth + ellipseWidth) {
lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
}
setText(text.subSequence(0, end) + ellipsis);
}
// Some devices try to auto adjust line spacing, so force default line spacing
// and invalidate the layout as a side effect
textPaint.setTextSize(newTextSize);
setLineSpacing(spacingAdd, spacingMult);
// Notify the listener if registered
if (textResizeListener != null) {
textResizeListener.onTextResize(this, oldTextSize, newTextSize);
}
// Reset force resize flag
needsResize = false;
}
private void logDimensions(float newTextSize, int newTextHeight) {
Log.d(this.getClass().getName(), "Nex text size: " + newTextSize + ", new text height: " + newTextHeight);
}
// Set the text size of the text paint object and use a static layout to render text off screen before measuring
private int getTextRect(CharSequence source, TextPaint paint, int width, float textSize) {
// Update the text paint object
paint.setTextSize(textSize);
// Draw using a static layout
StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, spacingMult, spacingAdd, false);
layout.draw(textResizeCanvas);
return layout.getHeight();
}
}

View File

@@ -1,22 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.view;
/**
* User: serso
* Date: 9/13/11
* Time: 12:08 AM
*/
public interface CursorControl {
public void setCursorOnStart();
public void setCursorOnEnd();
public void moveCursorLeft();
public void moveCursorRight();
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view;
import android.content.Context;
import org.jetbrains.annotations.NotNull;
/**
* User: serso
* Date: 12/18/11
* Time: 1:34 PM
*/
public class EnumMenu<T extends Enum & AMenuItem<D>, D> implements AMenu<T, D> {
@NotNull
private final AMenu<T, D> menu;
@NotNull
public static <T extends Enum & AMenuItem<D>, D> AMenu<T, D> newInstance(@NotNull Class<T> enumClass) {
return new EnumMenu<T, D>(enumClass);
}
private EnumMenu(Class<T> enumClass) {
this.menu = MenuImpl.newInstance(enumClass.getEnumConstants());
}
@Override
public T itemAt(int i) {
return this.menu.itemAt(i);
}
@NotNull
@Override
public CharSequence[] getMenuCaptions(@NotNull final Context context) {
return this.menu.getMenuCaptions(context);
}
}

View File

@@ -1,19 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.view;
import android.widget.TextView;
import org.jetbrains.annotations.NotNull;
/**
* User: serso
* Date: 9/10/11
* Time: 7:21 PM
*/
public interface FontSizeAdjuster {
void adjustFontSize(@NotNull TextView textView);
}

View File

@@ -1,24 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.view;
import org.jetbrains.annotations.NotNull;
import org.solovyev.common.utils.history.HistoryAction;
/**
* User: serso
* Date: 9/16/11
* Time: 11:42 PM
*/
public interface HistoryControl<T> {
void doHistoryAction(@NotNull HistoryAction historyAction);
void setCurrentHistoryState(@NotNull T editorHistoryState);
@NotNull
T getCurrentHistoryState();
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright (c) 2009-2012. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view;
import android.app.TabActivity;
import android.preference.PreferenceManager;
import android.widget.TabHost;
import org.jetbrains.annotations.NotNull;
import org.solovyev.android.view.prefs.StringPreference;
/**
* User: serso
* Date: 1/9/12
* Time: 6:17 PM
*/
public class LastTabSaver implements TabHost.OnTabChangeListener {
private static final String LAST_OPENED_TAB_P_KEY = "last_opened_tab_";
@NotNull
private final StringPreference<String> preference;
@NotNull
private final TabActivity tabActivity;
public LastTabSaver(@NotNull TabActivity tabActivity, @NotNull String defaultTabId) {
this.tabActivity = tabActivity;
this.preference = StringPreference.newInstance(getPreferenceKey(), defaultTabId);
final TabHost tabHost = tabActivity.getTabHost();
tabHost.setCurrentTabByTag(this.getLastOpenedTabId());
tabHost.setOnTabChangedListener(this);
}
public void destroy() {
final TabHost tabHost = tabActivity.getTabHost();
tabHost.setOnTabChangedListener(null);
}
@Override
public void onTabChanged(String tabId) {
preference.putPreference(PreferenceManager.getDefaultSharedPreferences(tabActivity), tabId);
}
@NotNull
public String getLastOpenedTabId() {
return preference.getPreference(PreferenceManager.getDefaultSharedPreferences(tabActivity));
}
@NotNull
private String getPreferenceKey() {
return LAST_OPENED_TAB_P_KEY + tabActivity.getClass().getName();
}
}

View File

@@ -1,63 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view;
import android.content.Context;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.utils.CollectionsUtils;
import java.util.ArrayList;
import java.util.List;
/**
* User: serso
* Date: 12/18/11
* Time: 1:31 PM
*/
public class MenuImpl<T extends AMenuItem<D>, D> implements AMenu<T, D> {
private final List<T> menuItems = new ArrayList<T>();
@NotNull
public static <T extends AMenuItem<D>, D> AMenu<T, D> newInstance(T... menuItems) {
return new MenuImpl<T, D>(menuItems);
}
@NotNull
public static <T extends AMenuItem<D>, D> AMenu<T, D> newInstance(@NotNull List<T> menuItems) {
return new MenuImpl<T, D>(menuItems);
}
private MenuImpl(T... menuItems) {
this(CollectionsUtils.asList(menuItems));
}
private MenuImpl(@NotNull List<T> menuItems) {
this.menuItems.addAll(menuItems);
}
@Override
@Nullable
public T itemAt(int i) {
if (i >= 0 && i < menuItems.size()) {
return menuItems.get(i);
} else {
return null;
}
}
@Override
@NotNull
public CharSequence[] getMenuCaptions(@NotNull final Context context) {
final CharSequence[] result = new CharSequence[this.menuItems.size()];
for (int i = 0; i < this.menuItems.size(); i++) {
result[i] = this.menuItems.get(i).getCaption(context);
}
return result;
}
}

View File

@@ -1,180 +0,0 @@
package org.solovyev.android.view.prefs;
import android.content.Context;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.utils.Mapper;
/**
* User: serso
* Date: 9/19/11
* Time: 3:17 PM
*/
public abstract class AbstractDialogPreference<T> extends DialogPreference {
@NotNull
protected final static String localNameSpace = "http://schemas.android.com/apk/res/org.solovyev.android.calculator";
@NotNull
protected final static String androidns = "http://schemas.android.com/apk/res/android";
@Nullable
protected TextView valueTextView;
@Nullable
protected String valueText;
@NotNull
protected final Context context;
@Nullable
protected String description;
@Nullable
protected T value;
@Nullable
private T defaultValue;
@Nullable
private final String defaultStringValue;
private final boolean needValueText;
public AbstractDialogPreference(Context context, AttributeSet attrs, @Nullable String defaultStringValue, boolean needValueText) {
super(context, attrs);
this.context = context;
this.defaultStringValue = defaultStringValue;
this.needValueText = needValueText;
final String defaultValueFromAttrs = attrs.getAttributeValue(androidns, "defaultValue");
if ( defaultValueFromAttrs != null ) {
defaultValue = getMapper().parseValue(defaultValueFromAttrs);
} else if (defaultStringValue != null) {
defaultValue = getMapper().parseValue(defaultStringValue);
} else {
throw new IllegalArgumentException();
}
description = attrs.getAttributeValue(androidns, "dialogMessage");
valueText = attrs.getAttributeValue(androidns, "text");
}
@Override
@NotNull
protected final LinearLayout onCreateDialogView() {
if (shouldPersist()) {
value = getPersistedValue();
}
final LinearLayout result = new LinearLayout(context);
result.setOrientation(LinearLayout.VERTICAL);
result.setPadding(6, 6, 6, 6);
if (description != null) {
final TextView splashText = new TextView(context);
splashText.setText(description);
result.addView(splashText);
}
if (needValueText) {
valueTextView = new TextView(context);
valueTextView.setGravity(Gravity.CENTER_HORIZONTAL);
valueTextView.setTextSize(32);
final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
result.addView(valueTextView, params);
}
final View v = createPreferenceView();
initPreferenceView(v);
final LinearLayout.LayoutParams params = getParams();
if (params != null) {
result.addView(v, params);
} else {
result.addView(v);
}
return result;
}
@Nullable
protected abstract LinearLayout.LayoutParams getParams();
@Override
protected void onSetInitialValue(boolean restore, Object defaultValue) {
super.onSetInitialValue(restore, defaultValue);
if (restore) {
if (shouldPersist()) {
value = getPersistedValue();
} else {
value = this.defaultValue;
}
} else {
value = (T) defaultValue;
if (shouldPersist()) {
persist(this.value);
}
}
}
@Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
initPreferenceView(null);
}
@NotNull
protected abstract View createPreferenceView();
protected abstract void initPreferenceView(@Nullable View v);
@Nullable
private T getPersistedValue() {
String persistedString = getPersistedString(defaultStringValue);
if ( persistedString == defaultStringValue ) {
return defaultValue;
} else {
return getMapper().parseValue(persistedString);
}
}
protected void persistValue(@Nullable T value) {
Log.d(AbstractDialogPreference.class.getName(), "Trying to persist value: " + value);
this.value = value;
Log.d(AbstractDialogPreference.class.getName(), "android.preference.Preference.callChangeListener()");
if (callChangeListener(value)) {
Log.d(AbstractDialogPreference.class.getName(), "android.preference.Preference.shouldPersist()");
if (shouldPersist()) {
Log.d(AbstractDialogPreference.class.getName(), "org.solovyev.android.view.prefs.AbstractDialogPreference.persist()");
persist(value);
}
}
}
private void persist(@Nullable T value) {
if (value != null) {
final String toBePersistedString = getMapper().formatValue(value);
if (toBePersistedString != null) {
if ( callChangeListener(toBePersistedString) ) {
persistString(toBePersistedString);
}
}
}
}
@NotNull
protected abstract Mapper<T> getMapper();
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.prefs;
import android.content.SharedPreferences;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* User: serso
* Date: 12/25/11
* Time: 12:23 PM
*/
public abstract class AbstractPreference<T> implements Preference<T> {
@NotNull
private final String key;
private final T defaultValue;
protected AbstractPreference(@NotNull String key, @Nullable T defaultValue) {
this.key = key;
this.defaultValue = defaultValue;
}
@NotNull
public String getKey() {
return key;
}
public T getDefaultValue() {
return defaultValue;
}
@Override
public final T getPreference(@NotNull SharedPreferences preferences) {
if ( preferences.contains(this.key) ) {
return getPersistedValue(preferences);
} else {
return this.defaultValue;
}
}
@Nullable
protected abstract T getPersistedValue(@NotNull SharedPreferences preferences);
@Override
public void putDefault(@NotNull SharedPreferences preferences) {
putPreference(preferences, this.defaultValue);
}
@Override
public void putPreference(@NotNull SharedPreferences preferences, @Nullable T value) {
if (value != null) {
final SharedPreferences.Editor editor = preferences.edit();
putPersistedValue(editor, value);
editor.commit();
}
}
protected abstract void putPersistedValue(@NotNull SharedPreferences.Editor editor, @NotNull T value);
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright (c) 2009-2012. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.prefs;
import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.google.ads.AdView;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.CalculatorApplication;
/**
* User: serso
* Date: 1/14/12
* Time: 6:47 PM
*/
public class AdViewPreference extends android.preference.Preference {
@Nullable
private AdView adView;
public AdViewPreference(Context context) {
super(context, null);
}
public AdViewPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected View onCreateView(ViewGroup parent) {
// this will create the linear layout defined in ads_layout.xml
View view = super.onCreateView(parent);
if (view instanceof ViewGroup) {
adView = CalculatorApplication.inflateAd((Activity) getContext(), ((ViewGroup) view), 0);
}
return view;
}
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.prefs;
import android.content.SharedPreferences;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* User: serso
* Date: 12/25/11
* Time: 1:06 PM
*/
public class BooleanPreference extends AbstractPreference<Boolean>{
public BooleanPreference(@NotNull String key, @Nullable Boolean defaultValue) {
super(key, defaultValue);
}
@Override
protected Boolean getPersistedValue(@NotNull SharedPreferences preferences) {
return preferences.getBoolean(getKey(), false);
}
@Override
protected void putPersistedValue(@NotNull SharedPreferences.Editor editor, @NotNull Boolean value) {
editor.putBoolean(getKey(), value);
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.prefs;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.utils.Mapper;
/**
* User: serso
* Date: 12/25/11
* Time: 1:17 PM
*/
public class EnumMapper<T extends Enum> implements Mapper<T>{
@NotNull
private final Class<T> enumClass;
public EnumMapper(@NotNull Class<T> enumClass) {
this.enumClass = enumClass;
}
public static <T extends Enum> Mapper<T> newInstance(@NotNull Class<T> enumClass) {
return new EnumMapper<T>(enumClass);
}
@Override
public String formatValue(@Nullable T value) throws IllegalArgumentException {
return value == null ? null : value.name();
}
@Override
public T parseValue(@Nullable String value) throws IllegalArgumentException {
return value == null ? null : (T)Enum.valueOf(enumClass, value);
}
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.prefs;
import android.content.SharedPreferences;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* User: serso
* Date: 12/25/11
* Time: 1:08 PM
*/
public class FloatPreference extends AbstractPreference<Float> {
protected FloatPreference(@NotNull String key, @Nullable Float defaultValue) {
super(key, defaultValue);
}
@Override
protected Float getPersistedValue(@NotNull SharedPreferences preferences) {
return preferences.getFloat(getKey(), -1f);
}
@Override
protected void putPersistedValue(@NotNull SharedPreferences.Editor editor, @NotNull Float value) {
editor.putFloat(getKey(), value);
}
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.prefs;
import android.content.Context;
import android.util.AttributeSet;
import org.jetbrains.annotations.NotNull;
import org.solovyev.common.NumberIntervalMapper;
import org.solovyev.common.utils.Interval;
import org.solovyev.common.utils.Mapper;
/**
* User: serso
* Date: 9/21/11
* Time: 11:41 PM
*/
public class FloatRangeSeekBarPreference extends RangeSeekBarPreference<Float> {
public FloatRangeSeekBarPreference(@NotNull Context context, AttributeSet attrs) {
super(context, attrs);
}
@NotNull
@Override
protected Mapper<Interval<Float>> getMapper() {
return new NumberIntervalMapper<Float>(Float.class);
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.prefs;
import android.content.SharedPreferences;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* User: serso
* Date: 12/25/11
* Time: 12:47 PM
*/
public class IntegerPreference extends AbstractPreference<Integer> {
public IntegerPreference(@NotNull String key, @Nullable Integer defaultValue) {
super(key, defaultValue);
}
@Override
protected Integer getPersistedValue(@NotNull SharedPreferences preferences) {
return preferences.getInt(getKey(), -1);
}
@Override
protected void putPersistedValue(@NotNull SharedPreferences.Editor editor, @NotNull Integer value) {
editor.putInt(getKey(), value);
}
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.prefs;
import android.content.Context;
import android.util.AttributeSet;
import org.jetbrains.annotations.NotNull;
import org.solovyev.common.NumberIntervalMapper;
import org.solovyev.common.utils.*;
/**
* User: serso
* Date: 9/19/11
* Time: 10:04 PM
*/
public class IntegerRangeSeekBarPreference extends RangeSeekBarPreference<Integer> {
public IntegerRangeSeekBarPreference(@NotNull Context context, AttributeSet attrs) {
super(context, attrs);
}
@NotNull
@Override
protected Mapper<Interval<Integer>> getMapper() {
return new NumberIntervalMapper<Integer>(Integer.class);
}
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.prefs;
import android.content.SharedPreferences;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* User: serso
* Date: 12/25/11
* Time: 1:07 PM
*/
public class LongPreference extends AbstractPreference<Long> {
protected LongPreference(@NotNull String key, @Nullable Long defaultValue) {
super(key, defaultValue);
}
@Override
protected Long getPersistedValue(@NotNull SharedPreferences preferences) {
return preferences.getLong(getKey(), -1);
}
@Override
protected void putPersistedValue(@NotNull SharedPreferences.Editor editor, @NotNull Long value) {
editor.putLong(getKey(), value);
}
}

View File

@@ -1,87 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.prefs;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.LinearLayout;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.view.widgets.NumberPicker;
import org.solovyev.common.NumberIntervalMapper;
import org.solovyev.common.utils.Interval;
import org.solovyev.common.utils.Mapper;
/**
* User: serso
* Date: 9/26/11
* Time: 10:31 PM
*/
public class NumberPickerDialogPreference extends AbstractDialogPreference<Integer> implements NumberPicker.OnChangedListener {
private static final NumberIntervalMapper<Integer> INTEGER_NUMBER_INTERVAL_MAPPER = new NumberIntervalMapper<Integer>(Integer.class);
@NotNull
private NumberPicker numberPicker;
@NotNull
private final Interval<Integer> boundaries;
public NumberPickerDialogPreference(Context context, AttributeSet attrs) {
super(context, attrs, null, false);
//noinspection ConstantConditions
boundaries = INTEGER_NUMBER_INTERVAL_MAPPER.parseValue(attrs.getAttributeValue(localNameSpace, "boundaries"));
createPreferenceView();
}
@Override
protected LinearLayout.LayoutParams getParams() {
final LinearLayout.LayoutParams result = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
result.gravity = Gravity.CENTER;
return result;
}
@NotNull
@Override
protected View createPreferenceView() {
this.numberPicker = new NumberPicker(context);
this.numberPicker.setOnChangeListener(this);
initPreferenceView(this.numberPicker);
return numberPicker;
}
@Override
protected void initPreferenceView(@Nullable View v) {
if ( v == null ) {
v = numberPicker;
}
if (value != null) {
((NumberPicker) v).setRange(boundaries.getLeftBorder(), boundaries.getRightBorder());
((NumberPicker) v).setCurrent(value);
}
}
@NotNull
@Override
protected Mapper<Integer> getMapper() {
return INTEGER_NUMBER_INTERVAL_MAPPER.getMapper();
}
@Override
public void onChanged(NumberPicker picker, int oldVal, int newVal) {
persistValue(newVal);
}
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.prefs;
import android.content.SharedPreferences;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* User: serso
* Date: 12/25/11
* Time: 12:21 PM
*/
public interface Preference<T> {
@NotNull
String getKey();
T getDefaultValue();
T getPreference(@NotNull SharedPreferences preferences);
void putPreference(@NotNull SharedPreferences preferences, @Nullable T value);
void putDefault(@NotNull SharedPreferences preferences);
}

View File

@@ -1,89 +0,0 @@
package org.solovyev.android.view.prefs;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.view.widgets.AbstractRangeSeekBar;
import org.solovyev.android.view.widgets.NumberRangeSeekBar;
import org.solovyev.common.utils.Interval;
import org.solovyev.common.utils.NumberInterval;
/**
* User: serso
* Date: 9/19/11
* Time: 12:27 PM
*/
public abstract class RangeSeekBarPreference<T extends Number> extends AbstractDialogPreference<Interval<T>> implements AbstractRangeSeekBar.OnRangeSeekBarChangeListener<T> {
@NotNull
private AbstractRangeSeekBar<T> rangeSeekBar;
@NotNull
private final Interval<T> boundaries;
private Integer steps;
public RangeSeekBarPreference(@NotNull Context context, AttributeSet attrs) {
super(context, attrs, null, true);
//noinspection ConstantConditions
boundaries = getMapper().parseValue(attrs.getAttributeValue(localNameSpace, "boundaries"));
steps = attrs.getAttributeIntValue(localNameSpace, "steps", -1);
if ( steps.equals(-1) ) {
steps = null;
}
assert steps == null || steps >= 2;
createPreferenceView();
}
@NotNull
protected View createPreferenceView() {
this.rangeSeekBar = new NumberRangeSeekBar<T>(boundaries, steps, context);
this.rangeSeekBar.setNotifyWhileDragging(true);
this.rangeSeekBar.setOnRangeSeekBarChangeListener(this);
initPreferenceView(this.rangeSeekBar);
return this.rangeSeekBar;
}
@Override
protected LinearLayout.LayoutParams getParams() {
return null;
}
@Override
protected void initPreferenceView(@Nullable View v) {
if ( v == null ) {
v = rangeSeekBar;
}
if (value != null) {
((AbstractRangeSeekBar<T>) v).setSelectedMinValue(value.getLeftBorder());
((AbstractRangeSeekBar<T>) v).setSelectedMaxValue(value.getRightBorder());
setValueText(value);
}
}
@Override
public void rangeSeekBarValuesChanged(T minValue, T maxValue, boolean changeComplete) {
final Interval<T> interval = new NumberInterval<T>(minValue, maxValue);
if (changeComplete) {
persistValue(interval);
}
setValueText(interval);
}
private void setValueText(@NotNull Interval<T> interval) {
final String t = String.valueOf(interval);
valueTextView.setText(valueText == null ? t : t.concat(valueText));
}
}

View File

@@ -1,106 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.prefs;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.NumberMapper;
import org.solovyev.common.utils.Mapper;
/* The following code was written by Matthew Wiggins
* and is released under the APACHE 2.0 license
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
public class SeekBarPreference extends AbstractDialogPreference<Integer> implements SeekBar.OnSeekBarChangeListener {
@NotNull
private SeekBar seekBar;
private int max = 0;
public SeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs, "50", true);
max = attrs.getAttributeIntValue(androidns, "max", 100);
}
@Override
protected LinearLayout.LayoutParams getParams() {
return new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
}
@NotNull
@Override
protected View createPreferenceView() {
seekBar = new SeekBar(context);
seekBar.setOnSeekBarChangeListener(this);
return seekBar;
}
@Override
protected void initPreferenceView(@Nullable View v ) {
if ( v == null) {
v = seekBar;
}
((SeekBar) v).setMax(max);
if (value != null) {
((SeekBar) v).setProgress(value);
setValueText(value);
}
}
@NotNull
@Override
protected Mapper<Integer> getMapper() {
return new NumberMapper<Integer>(Integer.class);
}
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) {
setValueText(value);
persistValue(value);
}
private void setValueText(int value) {
String t = String.valueOf(value);
valueTextView.setText(valueText == null ? t : t.concat(valueText));
}
public void onStartTrackingTouch(SeekBar seek) {
}
public void onStopTrackingTouch(SeekBar seek) {
}
public void setMax(int max) {
this.max = max;
}
public int getMax() {
return max;
}
public void setProgress(int progress) {
value = progress;
seekBar.setProgress(progress);
}
public int getProgress() {
return value;
}
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.prefs;
import android.content.SharedPreferences;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.utils.Mapper;
import org.solovyev.common.utils.StringMapper;
/**
* User: serso
* Date: 12/25/11
* Time: 12:37 PM
*/
public class StringPreference<T> extends AbstractPreference<T> {
@NotNull
private final Mapper<T> mapper;
public StringPreference(@NotNull String id, @Nullable T defaultValue, @NotNull Mapper<T> mapper) {
super(id, defaultValue);
this.mapper = mapper;
}
@NotNull
public static StringPreference<String> newInstance(@NotNull String id, @Nullable String defaultValue) {
return new StringPreference<String>(id, defaultValue, new StringMapper());
}
@NotNull
public static <T> StringPreference<T> newInstance(@NotNull String id, @Nullable String defaultValue, @NotNull Mapper<T> parser) {
return new StringPreference<T>(id, parser.parseValue(defaultValue), parser);
}
@NotNull
public static <T extends Enum> StringPreference<T> newInstance(@NotNull String id, @Nullable T defaultValue, @NotNull Class<T> enumType) {
return new StringPreference<T>(id, defaultValue, new EnumMapper<T>(enumType));
}
@Override
protected T getPersistedValue(@NotNull SharedPreferences preferences) {
return mapper.parseValue(preferences.getString(getKey(), null));
}
@Override
protected void putPersistedValue(@NotNull SharedPreferences.Editor editor, @NotNull T value) {
editor.putString(getKey(), mapper.formatValue(value));
}
}

View File

@@ -1,422 +0,0 @@
package org.solovyev.android.view.widgets;
/**
* User: serso
* Date: 9/19/11
* Time: 3:30 PM
*/
import android.content.Context;
import android.graphics.*;
import android.graphics.Paint.Style;
import android.view.MotionEvent;
import android.widget.ImageView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.R;
import org.solovyev.common.math.LinearNormalizer;
import org.solovyev.common.math.Normalizer;
import org.solovyev.common.utils.Converter;
/**
* Widget that lets users select a minimum and maximum value on a given numerical range.
* The range value types can be one of Long, Double, Integer, Float, Short, Byte or BigDecimal.
*
* @param <T> The Number type of the range values. One of Long, Double, Integer, Float, Short, Byte or BigDecimal.
* @author Stephan Tittel (stephan.tittel@kom.tu-darmstadt.de)
*/
public abstract class AbstractRangeSeekBar<T> extends ImageView {
@NotNull
private final Paint paint = new Paint();
@NotNull
private final ThumbContainer tc;
@NotNull
private final Converter<T, Double> toDoubleConverter;
@NotNull
private final Converter<Double, T> toTConverter;
@NotNull
private final T minValue, maxValue;
@NotNull
private final Normalizer fromValueNormalizer;
@NotNull
private final Normalizer fromScreenNormalizer;
private double normalizedMinValue = 0d;
private double normalizedMaxValue = 1d;
private Thumb pressedThumb = null;
private boolean notifyWhileDragging = false;
@Nullable
private OnRangeSeekBarChangeListener<T> listener;
/**
* Creates a new RangeSeekBar.
*
* @param minValue The minimum value of the selectable range.
* @param maxValue The maximum value of the selectable range.
* @param steps number of steps to be used in range seek bar
* @param context parent context
* @throws IllegalArgumentException Will be thrown if min/max value types are not one of Long, Double, Integer, Float, Short, Byte or BigDecimal.
*/
public AbstractRangeSeekBar(@NotNull T minValue, @NotNull T maxValue, @Nullable Integer steps, Context context) throws IllegalArgumentException {
super(context);
this.minValue = minValue;
this.maxValue = maxValue;
this.toDoubleConverter = getToDoubleConverter();
this.toTConverter = getToTConverter();
fromValueNormalizer = new LinearNormalizer(toDoubleConverter.convert(minValue), toDoubleConverter.convert(maxValue));
tc = new ThumbContainer();
fromScreenNormalizer = new Normalizer() {
@Override
public double normalize(double value) {
int width = getWidth();
if (width <= 2 * tc.padding) {
// prevent division by zero, simply return 0.
return 0d;
} else {
double result = (value - tc.padding) / (width - 2 * tc.padding);
return Math.min(1d, Math.max(0d, result));
}
}
@Override
public double denormalize(double value) {
return (float) (tc.padding + value * (getWidth() - 2 * tc.padding));
}
};
}
@NotNull
protected abstract Converter<Double,T> getToTConverter();
@NotNull
protected abstract Converter<T,Double> getToDoubleConverter();
public boolean isNotifyWhileDragging() {
return notifyWhileDragging;
}
/**
* Should the widget notify the listener callback while the user is still dragging a thumb? Default is false.
*
* @param flag
*/
public void setNotifyWhileDragging(boolean flag) {
this.notifyWhileDragging = flag;
}
/**
* Returns the absolute minimum value of the range that has been set at construction time.
*
* @return The absolute minimum value of the range.
*/
@NotNull
public T getMinValue() {
return minValue;
}
/**
* Returns the absolute maximum value of the range that has been set at construction time.
*
* @return The absolute maximum value of the range.
*/
@NotNull
public T getMaxValue() {
return maxValue;
}
/**
* Returns the currently selected min value.
*
* @return The currently selected min value.
*/
public T getSelectedMinValue() {
return denormalizeValue(normalizedMinValue);
}
/**
* Sets the currently selected minimum value. The widget will be invalidated and redrawn.
*
* @param value The Number value to set the minimum value to. Will be clamped to given absolute minimum/maximum range.
*/
public void setSelectedMinValue(@NotNull T value) {
setNormalizedMinValue(normalizeValue(value));
}
/**
* Returns the currently selected max value.
*
* @return The currently selected max value.
*/
public T getSelectedMaxValue() {
return denormalizeValue(normalizedMaxValue);
}
/**
* Sets the currently selected maximum value. The widget will be invalidated and redrawn.
*
* @param value The Number value to set the maximum value to. Will be clamped to given absolute minimum/maximum range.
*/
public void setSelectedMaxValue(@NotNull T value) {
setNormalizedMaxValue(normalizeValue(value));
}
/**
* Registers given listener callback to notify about changed selected values.
*
* @param listener The listener to notify about changed selected values.
*/
public void setOnRangeSeekBarChangeListener(OnRangeSeekBarChangeListener<T> listener) {
this.listener = listener;
}
/**
* Handles thumb selection and movement. Notifies listener callback on certain events.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
pressedThumb = evalPressedThumb(event.getX());
invalidate();
break;
case MotionEvent.ACTION_MOVE:
if (pressedThumb != null) {
double value = convertToNormalizedValue(event.getX());
if (Thumb.MIN.equals(pressedThumb)) {
setNormalizedMinValue(value);
} else if (Thumb.MAX.equals(pressedThumb)) {
setNormalizedMaxValue(value);
}
if (notifyWhileDragging && listener != null) {
listener.rangeSeekBarValuesChanged(getSelectedMinValue(), getSelectedMaxValue(), false);
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
pressedThumb = null;
invalidate();
if (listener != null) {
listener.rangeSeekBarValuesChanged(getSelectedMinValue(), getSelectedMaxValue(), true);
}
break;
}
return true;
}
/**
* Ensures correct size of the widget.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 200;
if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) {
width = MeasureSpec.getSize(widthMeasureSpec);
}
int height = tc.thumbImage.getHeight();
if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) {
height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
}
setMeasuredDimension(width, height);
}
/**
* Draws the widget on the given canvas.
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw seek bar background line
final RectF rect = tc.getRect();
paint.setStyle(Style.FILL);
paint.setColor(Color.GRAY);
canvas.drawRect(rect, paint);
// draw seek bar active range line
rect.left = convertToScreenValue(normalizedMinValue);
rect.right = convertToScreenValue(normalizedMaxValue);
// orange color
paint.setColor(Color.rgb(255, 165, 0));
canvas.drawRect(rect, paint);
// draw minimum thumb
drawThumb(convertToScreenValue(normalizedMinValue), Thumb.MIN == pressedThumb, canvas);
// draw maximum thumb
drawThumb(convertToScreenValue(normalizedMaxValue), Thumb.MAX == pressedThumb, canvas);
}
/**
* Draws the "normal" resp. "pressed" thumb image on specified x-coordinate.
*
* @param normalizedToScreenValue The x-coordinate in screen space where to draw the image.
* @param pressed Is the thumb currently in "pressed" state?
* @param canvas The canvas to draw upon.
*/
private void drawThumb(float normalizedToScreenValue, boolean pressed, Canvas canvas) {
canvas.drawBitmap(tc.getImage(pressed), normalizedToScreenValue - tc.thumbHalfWidth, (float) ((0.5f * getHeight()) - tc.thumbHalfHeight), paint);
}
/**
* Decides which (if any) thumb is touched by the given x-coordinate.
*
* @param touchX The x-coordinate of a touch event in screen space.
* @return The pressed thumb or null if none has been touched.
*/
private Thumb evalPressedThumb(float touchX) {
Thumb result = null;
boolean minThumbPressed = isInThumbRange(touchX, normalizedMinValue);
boolean maxThumbPressed = isInThumbRange(touchX, normalizedMaxValue);
if (minThumbPressed && maxThumbPressed) {
// if both thumbs are pressed (they lie on top of each other), choose the one with more room to drag. this avoids "stalling" the thumbs in a corner, not being able to drag them apart anymore.
result = (touchX / getWidth() > 0.5f) ? Thumb.MIN : Thumb.MAX;
} else if (minThumbPressed) {
result = Thumb.MIN;
} else if (maxThumbPressed) {
result = Thumb.MAX;
}
return result;
}
/**
* Decides if given x-coordinate in screen space needs to be interpreted as "within" the normalized thumb x-coordinate.
*
* @param touchX The x-coordinate in screen space to check.
* @param normalizedThumbValue The normalized x-coordinate of the thumb to check.
* @return true if x-coordinate is in thumb range, false otherwise.
*/
private boolean isInThumbRange(float touchX, double normalizedThumbValue) {
return Math.abs(touchX - convertToScreenValue(normalizedThumbValue)) <= tc.thumbHalfWidth;
}
/**
* Sets normalized min value to value so that 0 <= value <= normalized max value <= 1.
* The View will get invalidated when calling this method.
*
* @param value The new normalized min value to set.
*/
private void setNormalizedMinValue(double value) {
normalizedMinValue = Math.max(0d, Math.min(1d, Math.min(value, normalizedMaxValue)));
invalidate();
}
/**
* Sets normalized max value to value so that 0 <= normalized min value <= value <= 1.
* The View will get invalidated when calling this method.
*
* @param value The new normalized max value to set.
*/
private void setNormalizedMaxValue(double value) {
normalizedMaxValue = Math.max(0d, Math.min(1d, Math.max(value, normalizedMinValue)));
invalidate();
}
/**
* Converts a normalized value to a Number object in the value space between absolute minimum and maximum.
*
* @param normalized
* @return
*/
@SuppressWarnings("unchecked")
private T denormalizeValue(double normalized) {
return toTConverter.convert(fromValueNormalizer.denormalize(normalized));
}
/**
* Converts the given Number value to a normalized double.
*
* @param value The Number value to normalize.
* @return The normalized double.
*/
private double normalizeValue(T value) {
return fromValueNormalizer.normalize(toDoubleConverter.convert(value));
}
/**
* Converts a normalized value into screen space.
*
* @param normalizedValue The normalized value to convert.
* @return The converted value in screen space.
*/
private float convertToScreenValue(double normalizedValue) {
return (float)this.fromScreenNormalizer.denormalize(normalizedValue);
}
/**
* Converts screen space x-coordinates into normalized values.
*
* @param screenValue The x-coordinate in screen space to convert.
* @return The normalized value.
*/
private double convertToNormalizedValue(float screenValue) {
return this.fromScreenNormalizer.normalize(screenValue);
}
/**
* Callback listener interface to notify about changed range values.
*
* @param <T> The Number type the RangeSeekBar has been declared with.
* @author Stephan Tittel (stephan.tittel@kom.tu-darmstadt.de)
*/
public interface OnRangeSeekBarChangeListener<T> {
void rangeSeekBarValuesChanged(T minValue, T maxValue, boolean changeComplete);
}
/**
* Thumb constants (min and max).
*
* @author Stephan Tittel (stephan.tittel@kom.tu-darmstadt.de)
*/
private static enum Thumb {
MIN, MAX
}
private class ThumbContainer {
@NotNull
private final Bitmap thumbImage = BitmapFactory.decodeResource(getResources(), R.drawable.seek_thumb_normal);
@NotNull
private final Bitmap thumbPressedImage = BitmapFactory.decodeResource(getResources(), R.drawable.seek_thumb_pressed);
private final float thumbWidth = thumbImage.getWidth();
private final float thumbHalfWidth = 0.5f * thumbWidth;
private final float thumbHalfHeight = 0.5f * thumbImage.getHeight();
private final float lineHeight = 0.3f * thumbHalfHeight;
private final float padding = thumbHalfWidth;
public RectF getRect() {
return new RectF(padding, 0.5f * (getHeight() - lineHeight), getWidth() - padding, 0.5f * (getHeight() + lineHeight));
}
public Bitmap getImage(boolean pressed) {
return pressed ? thumbPressedImage : thumbImage;
}
}
}

View File

@@ -1,254 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.view.widgets;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.drawable.Drawable;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;
import org.jetbrains.annotations.NotNull;
import org.solovyev.android.calculator.R;
import org.solovyev.android.view.FontSizeAdjuster;
import org.solovyev.common.utils.CollectionsUtils;
import org.solovyev.common.utils.Point2d;
import org.solovyev.common.utils.StringUtils;
import java.util.Arrays;
/**
* NOTE: copied from com.android.calculator2.ColorButton
*/
/**
* Button with click-animation effect.
*/
public class ColorButton extends Button {
private int CLICK_FEEDBACK_COLOR;
private static final int CLICK_FEEDBACK_INTERVAL = 10;
private static final int CLICK_FEEDBACK_DURATION = 350;
@NotNull
private Point2d textPosition;
private long animationStart;
private Paint feedbackPaint;
@NotNull
private final OnClickListenerVibrator onClickListener;
private static final float H_TEXT_POSITION_DEFAULT_VALUE = 0.5f;
private float hTextPosition = H_TEXT_POSITION_DEFAULT_VALUE;
public ColorButton(Context context, AttributeSet attrs) {
this(context, attrs, true);
}
public ColorButton(Context context, AttributeSet attrs, boolean init) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, org.solovyev.android.calculator.R.styleable.DragButton);
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
final String attrValue = a.getString(attr);
if (!StringUtils.isEmpty(attrValue)) {
switch (attr) {
case R.styleable.DragButton_hTextPosition:
this.hTextPosition = Float.valueOf(attrValue);
break;
}
}
}
if (init) {
init(context);
}
this.onClickListener = new OnClickListenerVibrator((Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE), PreferenceManager.getDefaultSharedPreferences(context));
}
protected void init(Context context) {
final Resources resources = getResources();
CLICK_FEEDBACK_COLOR = resources.getColor(org.solovyev.android.calculator.R.color.magic_flame);
feedbackPaint = new Paint();
feedbackPaint.setStyle(Style.STROKE);
feedbackPaint.setStrokeWidth(2);
if (CollectionsUtils.contains(getText().toString(), Arrays.asList("+", "-", "/", "×", "*", ""))) {
getPaint().setColor(resources.getColor(R.color.button_operator_text_color));
} else if (getText().toString().equals("C")) {
getPaint().setColor(resources.getColor(R.color.button_ce_text_color));
} else {
getPaint().setColor(resources.getColor(R.color.button_text_color));
}
animationStart = -1;
if (context instanceof FontSizeAdjuster) {
((FontSizeAdjuster) context).adjustFontSize(this);
}
}
@Override
public void onSizeChanged(int w, int h, int oldW, int oldH) {
measureText();
}
protected void measureText() {
Paint paint = getPaint();
if (getText() != null) {
textPosition = getTextPosition(paint, getText());
}
}
private Point2d getTextPosition(@NotNull Paint paint, @NotNull CharSequence text) {
final Point2d result = new Point2d();
result.setX(hTextPosition * getWidth() - 0.5f * paint.measureText(text.toString()));
float height = getHeight() - paint.ascent() - paint.descent();
result.setY(height / 2);
return result;
}
@Override
protected void onTextChanged(CharSequence text, int start, int before, int after) {
measureText();
}
public void drawMagicFlame(int duration, Canvas canvas) {
int alpha = 255 - 255 * duration / CLICK_FEEDBACK_DURATION;
int color = CLICK_FEEDBACK_COLOR | (alpha << 24);
feedbackPaint.setColor(color);
canvas.drawRect(1, 1, getWidth() - 1, getHeight() - 1, feedbackPaint);
}
@Override
public void onDraw(Canvas canvas) {
if (animationStart != -1) {
int animDuration = (int) (System.currentTimeMillis() - animationStart);
if (animDuration >= CLICK_FEEDBACK_DURATION) {
animationStart = -1;
} else {
drawMagicFlame(animDuration, canvas);
postInvalidateDelayed(CLICK_FEEDBACK_INTERVAL);
}
}
CharSequence text = getText();
if (!StringUtils.isEmpty(text) && textPosition != null) {
canvas.drawText(text, 0, text.length(), textPosition.getX(), textPosition.getY(), getPaint());
} else {
drawDrawables(canvas);
}
}
private void drawDrawables(Canvas canvas) {
final int compoundPaddingLeft = getCompoundPaddingLeft();
final int compoundPaddingTop = getCompoundPaddingTop();
final int compoundPaddingRight = getCompoundPaddingRight();
final int compoundPaddingBottom = getCompoundPaddingBottom();
final int scrollX = getScrollX();
final int scrollY = getScrollY();
final int right = getRight();
final int left = getLeft();
final int bottom = getBottom();
final int top = getTop();
final Drawable[] drawables = getCompoundDrawables();
if (drawables != null) {
int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
Drawable topDr = drawables[1];
// IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
if (topDr != null) {
canvas.save();
canvas.translate(scrollX + compoundPaddingLeft + (hspace - topDr.getBounds().width()) / 2,
scrollY + getPaddingTop() + vspace / 2);
topDr.draw(canvas);
canvas.restore();
}
}
}
public void animateClickFeedback() {
animationStart = System.currentTimeMillis();
invalidate();
}
@Override
public boolean performClick() {
vibrate();
return super.performClick();
}
@Override
public boolean performLongClick() {
vibrate();
return super.performLongClick();
}
private void vibrate() {
this.onClickListener.onClick(this);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
animateClickFeedback();
break;
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_CANCEL:
invalidate();
break;
}
return result;
}
}

View File

@@ -1,234 +0,0 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.solovyev.android.view.widgets;
import android.os.Bundle;
import android.app.Dialog;
import android.content.Context;
import android.graphics.*;
import android.view.MotionEvent;
import android.view.View;
public class ColorPickerDialog extends Dialog {
public interface OnColorChangedListener {
void colorChanged(int color);
}
private OnColorChangedListener mListener;
private int mInitialColor;
private static class ColorPickerView extends View {
private Paint mPaint;
private Paint mCenterPaint;
private final int[] mColors;
private OnColorChangedListener mListener;
ColorPickerView(Context c, OnColorChangedListener l, int color) {
super(c);
mListener = l;
mColors = new int[]{
0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00,
0xFFFFFF00, 0xFFFF0000
};
Shader s = new SweepGradient(0, 0, mColors, null);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setShader(s);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(32);
mCenterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCenterPaint.setColor(color);
mCenterPaint.setStrokeWidth(5);
}
private boolean mTrackingCenter;
private boolean mHighlightCenter;
@Override
protected void onDraw(Canvas canvas) {
float r = CENTER_X - mPaint.getStrokeWidth() * 0.5f;
canvas.translate(CENTER_X, CENTER_X);
canvas.drawOval(new RectF(-r, -r, r, r), mPaint);
canvas.drawCircle(0, 0, CENTER_RADIUS, mCenterPaint);
if (mTrackingCenter) {
int c = mCenterPaint.getColor();
mCenterPaint.setStyle(Paint.Style.STROKE);
if (mHighlightCenter) {
mCenterPaint.setAlpha(0xFF);
} else {
mCenterPaint.setAlpha(0x80);
}
canvas.drawCircle(0, 0,
CENTER_RADIUS + mCenterPaint.getStrokeWidth(),
mCenterPaint);
mCenterPaint.setStyle(Paint.Style.FILL);
mCenterPaint.setColor(c);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(CENTER_X * 2, CENTER_Y * 2);
}
private static final int CENTER_X = 100;
private static final int CENTER_Y = 100;
private static final int CENTER_RADIUS = 32;
private int floatToByte(float x) {
int n = java.lang.Math.round(x);
return n;
}
private int pinToByte(int n) {
if (n < 0) {
n = 0;
} else if (n > 255) {
n = 255;
}
return n;
}
private int ave(int s, int d, float p) {
return s + java.lang.Math.round(p * (d - s));
}
private int interpColor(int colors[], float unit) {
if (unit <= 0) {
return colors[0];
}
if (unit >= 1) {
return colors[colors.length - 1];
}
float p = unit * (colors.length - 1);
int i = (int) p;
p -= i;
// now p is just the fractional part [0...1) and i is the index
int c0 = colors[i];
int c1 = colors[i + 1];
int a = ave(Color.alpha(c0), Color.alpha(c1), p);
int r = ave(Color.red(c0), Color.red(c1), p);
int g = ave(Color.green(c0), Color.green(c1), p);
int b = ave(Color.blue(c0), Color.blue(c1), p);
return Color.argb(a, r, g, b);
}
private int rotateColor(int color, float rad) {
float deg = rad * 180 / 3.1415927f;
int r = Color.red(color);
int g = Color.green(color);
int b = Color.blue(color);
ColorMatrix cm = new ColorMatrix();
ColorMatrix tmp = new ColorMatrix();
cm.setRGB2YUV();
tmp.setRotate(0, deg);
cm.postConcat(tmp);
tmp.setYUV2RGB();
cm.postConcat(tmp);
final float[] a = cm.getArray();
int ir = floatToByte(a[0] * r + a[1] * g + a[2] * b);
int ig = floatToByte(a[5] * r + a[6] * g + a[7] * b);
int ib = floatToByte(a[10] * r + a[11] * g + a[12] * b);
return Color.argb(Color.alpha(color), pinToByte(ir),
pinToByte(ig), pinToByte(ib));
}
private static final float PI = 3.1415926f;
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX() - CENTER_X;
float y = event.getY() - CENTER_Y;
boolean inCenter = java.lang.Math.sqrt(x * x + y * y) <= CENTER_RADIUS;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mTrackingCenter = inCenter;
if (inCenter) {
mHighlightCenter = true;
invalidate();
break;
}
case MotionEvent.ACTION_MOVE:
if (mTrackingCenter) {
if (mHighlightCenter != inCenter) {
mHighlightCenter = inCenter;
invalidate();
}
} else {
float angle = (float) java.lang.Math.atan2(y, x);
// need to turn angle [-PI ... PI] into unit [0....1]
float unit = angle / (2 * PI);
if (unit < 0) {
unit += 1;
}
mCenterPaint.setColor(interpColor(mColors, unit));
invalidate();
}
break;
case MotionEvent.ACTION_UP:
if (mTrackingCenter) {
if (inCenter) {
mListener.colorChanged(mCenterPaint.getColor());
}
mTrackingCenter = false; // so we draw w/o halo
invalidate();
}
break;
}
return true;
}
}
public ColorPickerDialog(Context context,
OnColorChangedListener listener,
int initialColor) {
super(context);
mListener = listener;
mInitialColor = initialColor;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
OnColorChangedListener l = new OnColorChangedListener() {
public void colorChanged(int color) {
mListener.colorChanged(color);
dismiss();
}
};
setContentView(new ColorPickerView(getContext(), l, mInitialColor));
setTitle("Pick a Color");
}
}

View File

@@ -1,395 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.view.widgets;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.TextPaint;
import android.util.AttributeSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.R;
import org.solovyev.common.NumberParser;
import org.solovyev.common.utils.CollectionsUtils;
import org.solovyev.common.utils.Point2d;
import org.solovyev.common.utils.StringUtils;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* User: serso
* Date: 7/17/11
* Time: 10:25 PM
*/
public class DirectionDragButton extends DragButton {
@NotNull
private final static Float DEFAULT_DIRECTION_TEXT_SCALE_FLOAT = 0.33f;
@NotNull
private final static Integer DEFAULT_DIRECTION_TEXT_ALPHA = 140;
@NotNull
private final static String DEFAULT_DIRECTION_TEXT_SCALE = "0.33;0.33;0.33;0.33";
protected static class DirectionTextData {
@NotNull
private final GuiDragDirection guiDragDirection;
@NotNull
private String text;
@NotNull
private Point2d position;
@NotNull
private TextPaint paint;
@NotNull
private Float textScale;
private DirectionTextData(@NotNull GuiDragDirection guiDragDirection, @NotNull String text) {
this.guiDragDirection = guiDragDirection;
this.text = text;
}
@NotNull
public GuiDragDirection getGuiDragDirection() {
return guiDragDirection;
}
@NotNull
public String getText() {
return text;
}
public void setText(@NotNull String text) {
this.text = text;
}
@NotNull
public Point2d getPosition() {
return position;
}
public void setPosition(@NotNull Point2d position) {
this.position = position;
}
@NotNull
public TextPaint getPaint() {
return paint;
}
public void setPaint(@NotNull TextPaint paint) {
this.paint = paint;
}
@NotNull
public Float getTextScale() {
return textScale;
}
public void setTextScale(@NotNull Float textScale) {
this.textScale = textScale;
}
}
protected static enum GuiDragDirection {
up(DragDirection.up, 0) {
@Override
public int getAttributeId() {
return R.styleable.DirectionDragButton_textUp;
}
@NotNull
@Override
public Point2d getTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, @NotNull CharSequence text, CharSequence baseText, int w, int h) {
return getUpDownTextPosition(paint, basePaint, text, baseText, 1, w, h);
}
},
down(DragDirection.down, 2) {
@Override
public int getAttributeId() {
return R.styleable.DirectionDragButton_textDown;
}
@NotNull
@Override
public Point2d getTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, @NotNull CharSequence text, CharSequence baseText, int w, int h) {
return getUpDownTextPosition(paint, basePaint, text, baseText, -1, w, h);
}
},
left(DragDirection.left, 3) {
@Override
public int getAttributeId() {
return R.styleable.DirectionDragButton_textLeft;
}
@NotNull
@Override
public Point2d getTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, @NotNull CharSequence text, CharSequence baseText, int w, int h) {
return getLeftRightTextPosition(paint, basePaint, text, baseText, w, h, true);
}
},
right(DragDirection.right, 1) {
@Override
public int getAttributeId() {
return R.styleable.DirectionDragButton_textRight;
}
@NotNull
@Override
public Point2d getTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, @NotNull CharSequence text, CharSequence baseText, int w, int h) {
return getLeftRightTextPosition(paint, basePaint, text, baseText, w, h, false);
}
};
@NotNull
private final DragDirection dragDirection;
private final int attributePosition;
GuiDragDirection(@NotNull DragDirection dragDirection, int attributePosition) {
this.dragDirection = dragDirection;
this.attributePosition = attributePosition;
}
public abstract int getAttributeId();
public int getAttributePosition() {
return attributePosition;
}
@NotNull
public abstract Point2d getTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, @NotNull CharSequence text, CharSequence baseText, int w, int h);
@NotNull
private static Point2d getLeftRightTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, CharSequence text, @NotNull CharSequence baseText, int w, int h, boolean left) {
final Point2d result = new Point2d();
if (left) {
float width = paint.measureText(" ");
result.setX(width);
} else {
float width = paint.measureText(text.toString() + " ");
result.setX(w - width);
}
float selfHeight = paint.ascent() + paint.descent();
basePaint.measureText(StringUtils.getNotEmpty(baseText, "|"));
result.setY(h / 2 - selfHeight / 2);
return result;
}
@NotNull
private static Point2d getUpDownTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, @NotNull CharSequence text, CharSequence baseText, float direction, int w, int h) {
final Point2d result = new Point2d();
float width = paint.measureText(text.toString() + " ");
result.setX(w - width);
float selfHeight = paint.ascent() + paint.descent();
basePaint.measureText(StringUtils.getNotEmpty(baseText, "|"));
if (direction < 0) {
result.setY(h / 2 + h / 3 - selfHeight / 2);
} else {
result.setY(h / 2 - h / 3 - selfHeight / 2);
}
return result;
}
@Nullable
public static GuiDragDirection valueOf(@NotNull DragDirection dragDirection) {
for (GuiDragDirection guiDragDirection : values()) {
if (guiDragDirection.dragDirection == dragDirection) {
return guiDragDirection;
}
}
return null;
}
}
@NotNull
private final Map<GuiDragDirection, DirectionTextData> directionTextDataMap = new EnumMap<GuiDragDirection, DirectionTextData>(GuiDragDirection.class);
@NotNull
private String directionTextScale = DEFAULT_DIRECTION_TEXT_SCALE;
@NotNull
private Integer directionTextAlpha = DEFAULT_DIRECTION_TEXT_ALPHA;
private boolean initialized = false;
public DirectionDragButton(Context context, @NotNull AttributeSet attrs) {
super(context, attrs, false);
init(context, attrs);
}
private void init(@NotNull Context context, @NotNull AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DirectionDragButton);
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
final String attrValue = a.getString(attr);
if (!StringUtils.isEmpty(attrValue)) {
switch (attr) {
case R.styleable.DirectionDragButton_directionTextScale:
this.directionTextScale = attrValue;
break;
case R.styleable.DirectionDragButton_directionTextAlpha:
this.directionTextAlpha = Integer.valueOf(attrValue);
break;
default:
// try drag direction text
for (GuiDragDirection guiDragDirection : GuiDragDirection.values()) {
if (guiDragDirection.getAttributeId() == attr) {
this.directionTextDataMap.put(guiDragDirection, new DirectionTextData(guiDragDirection, attrValue));
break;
}
}
break;
}
}
}
for (Map.Entry<GuiDragDirection, Float> entry : getDirectionTextScales().entrySet()) {
final DirectionTextData dtd = directionTextDataMap.get(entry.getKey());
if (dtd != null) {
dtd.setTextScale(entry.getValue());
}
}
super.init(context);
initialized = true;
}
@Override
protected void measureText() {
super.measureText();
if (initialized) {
final Paint basePaint = getPaint();
final Resources resources = getResources();
for (DirectionTextData directionTextData : directionTextDataMap.values()) {
initDirectionTextPaint(basePaint, directionTextData, resources);
final GuiDragDirection guiDragDirection = directionTextData.getGuiDragDirection();
final String directionText = directionTextData.getText();
final Paint directionPaint = directionTextData.getPaint();
directionTextData.setPosition(guiDragDirection.getTextPosition(directionPaint, basePaint, directionText, getText(), getWidth(), getHeight()));
}
}
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
final TextPaint paint = getPaint();
final Resources resources = getResources();
for (DirectionTextData directionTextData : directionTextDataMap.values()) {
initDirectionTextPaint(paint, directionTextData, resources);
final String text = directionTextData.getText();
final Point2d position = directionTextData.getPosition();
canvas.drawText(text, 0, text.length(), position.getX(), position.getY(), directionTextData.getPaint());
}
}
protected void initDirectionTextPaint(@NotNull Paint basePaint,
@NotNull DirectionTextData directionTextData,
@NotNull Resources resources) {
final TextPaint directionTextPaint = new TextPaint(basePaint);
directionTextPaint.setColor(resources.getColor(R.color.button_text_color));
directionTextPaint.setAlpha(getDirectionTextAlpha());
directionTextPaint.setTextSize(basePaint.getTextSize() * directionTextData.getTextScale());
directionTextData.setPaint(directionTextPaint);
}
protected int getDirectionTextAlpha() {
return directionTextAlpha;
}
@Nullable
public String getTextUp() {
return getText(GuiDragDirection.up);
}
@Nullable
public String getTextDown() {
return getText(GuiDragDirection.down);
}
@Nullable
public String getText(@NotNull DragDirection direction) {
final GuiDragDirection guiDragDirection = GuiDragDirection.valueOf(direction);
return guiDragDirection == null ? null : getText(guiDragDirection);
}
@Nullable
private String getText(@NotNull GuiDragDirection direction) {
DirectionTextData directionTextData = this.directionTextDataMap.get(direction);
return directionTextData == null ? null : directionTextData.getText();
}
@NotNull
public String getDirectionTextScale() {
return directionTextScale;
}
@NotNull
private Map<GuiDragDirection, Float> getDirectionTextScales() {
final List<Float> scales = CollectionsUtils.split(getDirectionTextScale(), ";", new NumberParser<Float>(Float.class));
final Map<GuiDragDirection, Float> result = new HashMap<GuiDragDirection, Float>();
for (GuiDragDirection guiDragDirection : GuiDragDirection.values()) {
result.put(guiDragDirection, DEFAULT_DIRECTION_TEXT_SCALE_FLOAT);
}
if (scales.size() == 1) {
final Float scale = scales.get(0);
for (Map.Entry<GuiDragDirection, Float> entry : result.entrySet()) {
entry.setValue(scale);
}
} else {
for (int i = 0; i < scales.size(); i++) {
for (GuiDragDirection guiDragDirection : GuiDragDirection.values()) {
if (guiDragDirection.getAttributePosition() == i) {
result.put(guiDragDirection, scales.get(i));
}
}
}
}
return result;
}
}

View File

@@ -1,108 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.view.widgets;
import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.utils.Point2d;
public class DragButton extends ColorButton {
@Nullable
private Point2d startPoint = null;
@Nullable
private org.solovyev.android.view.widgets.OnDragListener onDragListener;
private final OnTouchListener onTouchListener = new OnTouchListenerImpl();
public DragButton(Context context, @NotNull AttributeSet attrs) {
this(context, attrs, true);
}
public DragButton(Context context, @NotNull AttributeSet attrs, boolean init) {
super(context, attrs, false);
setOnTouchListener(this.onTouchListener);
if ( init ) {
super.init(context);
}
}
public void setOnDragListener(@Nullable org.solovyev.android.view.widgets.OnDragListener onDragListener) {
this.onDragListener = onDragListener;
}
@Nullable
public org.solovyev.android.view.widgets.OnDragListener getOnDragListener() {
return onDragListener;
}
/**
* OnTouchListener implementation that fires onDrag()
*
* @author serso
*
*/
private final class OnTouchListenerImpl implements OnTouchListener {
@Override
public boolean onTouch(@NotNull View v, @NotNull MotionEvent event) {
// processing on touch event
// in order to avoid possible NPEs
final Point2d localStartPoint = startPoint;
final org.solovyev.android.view.widgets.OnDragListener localOnDragListener = onDragListener;
if (localOnDragListener != null) {
// only if onDrag() listener specified
Log.d(String.valueOf(getId()), "onTouch() for: " + getId() + " . Motion event: " + event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// start tracking: set start point
startPoint = new Point2d(event.getX(), event.getY());
break;
case MotionEvent.ACTION_UP:
// stop tracking
if (localStartPoint != null && localOnDragListener.onDrag(DragButton.this, new DragEvent(localStartPoint, event))) {
if (localOnDragListener.isSuppressOnClickEvent()) {
// prevent on click action
setPressed(false);
// sometimes setPressed(false); doesn't work so to prevent onClick action button disables
if (v instanceof Button) {
final Button button = (Button) v;
button.setEnabled(false);
new Handler().postDelayed(new Runnable() {
public void run() {
button.setEnabled(true);
}
}, 200);
}
}
}
startPoint = null;
break;
}
}
return false;
}
}
}

View File

@@ -1,14 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.view.widgets;
public enum DragDirection {
up,
down,
left,
right;
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.view.widgets;
import android.view.MotionEvent;
import org.jetbrains.annotations.NotNull;
import org.solovyev.common.utils.Point2d;
public class DragEvent {
@NotNull
private final Point2d startPoint;
@NotNull
private final MotionEvent motionEvent;
public DragEvent(@NotNull Point2d startPoint, @NotNull MotionEvent motionEvent) {
this.startPoint = startPoint;
this.motionEvent = motionEvent;
}
/**
* @return motion event started at start point
*/
@NotNull
public MotionEvent getMotionEvent() {
return motionEvent;
}
/**
* @return start point of dragging
*/
@NotNull
public Point2d getStartPoint() {
return startPoint;
}
}

View File

@@ -1,20 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.view.widgets;
import org.jetbrains.annotations.NotNull;
import java.util.EventListener;
/**
* User: serso
* Date: 9/18/11
* Time: 8:48 PM
*/
public interface DragPreferencesChangeListener extends EventListener{
void onDragPreferencesChange(@NotNull SimpleOnDragListener.Preferences preferences );
}

View File

@@ -1,52 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.view.widgets;
import android.util.Log;
import android.view.MotionEvent;
import org.jetbrains.annotations.NotNull;
import org.solovyev.android.view.HistoryControl;
import org.solovyev.common.utils.Point2d;
import org.solovyev.common.utils.StringUtils;
import org.solovyev.common.utils.history.HistoryAction;
/**
* User: serso
* Date: 9/16/11
* Time: 11:36 PM
*/
public class HistoryDragProcessor<T> implements SimpleOnDragListener.DragProcessor {
@NotNull
private final HistoryControl<T> historyControl;
public HistoryDragProcessor(@NotNull HistoryControl<T> historyControl) {
this.historyControl = historyControl;
}
@Override
public boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent) {
boolean result = false;
Log.d(String.valueOf(dragButton.getId()), "History on drag event start: " + dragDirection);
final HistoryAction historyAction;
if ( dragDirection == DragDirection.up ) {
historyAction = HistoryAction.undo;
} else if ( dragDirection == DragDirection.down ) {
historyAction = HistoryAction.redo;
} else {
historyAction = null;
}
if (historyAction != null) {
result = true;
historyControl.doHistoryAction(historyAction);
}
return result;
}
}

View File

@@ -1,538 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.widgets;
/**
* User: serso
* Date: 9/18/11
* Time: 10:03 PM
*/
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context;
import android.os.Handler;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spanned;
import android.text.method.NumberKeyListener;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.solovyev.android.calculator.R;
/**
* A view for selecting a number
*
* For a dialog using this view, see {@link android.app.TimePickerDialog}.
* @hide
*/
public class NumberPicker extends LinearLayout {
/**
* The callback interface used to indicate the number value has been adjusted.
*/
public interface OnChangedListener {
/**
* @param picker The NumberPicker associated with this listener.
* @param oldVal The previous value.
* @param newVal The new value.
*/
void onChanged(NumberPicker picker, int oldVal, int newVal);
}
/**
* Interface used to format the number into a string for presentation
*/
public interface Formatter {
String toString(int value);
}
/*
* Use a custom NumberPicker formatting callback to use two-digit
* minutes strings like "01". Keeping a static formatter etc. is the
* most efficient way to do this; it avoids creating temporary objects
* on every call to format().
*/
public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER =
new NumberPicker.Formatter() {
final StringBuilder mBuilder = new StringBuilder();
final java.util.Formatter mFmt = new java.util.Formatter(
mBuilder, java.util.Locale.US);
final Object[] mArgs = new Object[1];
public String toString(int value) {
mArgs[0] = value;
mBuilder.delete(0, mBuilder.length());
mFmt.format("%02d", mArgs);
return mFmt.toString();
}
};
private final Handler mHandler;
private final Runnable mRunnable = new Runnable() {
public void run() {
if (mIncrement) {
changeCurrent(mCurrent + 1);
mHandler.postDelayed(this, mSpeed);
} else if (mDecrement) {
changeCurrent(mCurrent - 1);
mHandler.postDelayed(this, mSpeed);
}
}
};
private final EditText mText;
private final InputFilter mNumberInputFilter;
private String[] mDisplayedValues;
/**
* Lower value of the range of numbers allowed for the NumberPicker
*/
private int mStart;
/**
* Upper value of the range of numbers allowed for the NumberPicker
*/
private int mEnd;
/**
* Current value of this NumberPicker
*/
private int mCurrent;
/**
* Previous value of this NumberPicker.
*/
private int mPrevious;
private OnChangedListener mListener;
private Formatter mFormatter;
private long mSpeed = 300;
private boolean mIncrement;
private boolean mDecrement;
/**
* Create a new number picker
* @param context the application environment
*/
public NumberPicker(Context context) {
this(context, null);
}
/**
* Create a new number picker
* @param context the application environment
* @param attrs a collection of attributes
*/
public NumberPicker(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(VERTICAL);
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.number_picker, this, true);
mHandler = new Handler();
OnClickListener clickListener = new OnClickListener() {
public void onClick(View v) {
validateInput(mText);
if (!mText.hasFocus()) mText.requestFocus();
// now perform the increment/decrement
if (R.id.increment == v.getId()) {
changeCurrent(mCurrent + 1);
} else if (R.id.decrement == v.getId()) {
changeCurrent(mCurrent - 1);
}
}
};
OnFocusChangeListener focusListener = new OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
/* When focus is lost check that the text field
* has valid values.
*/
if (!hasFocus) {
validateInput(v);
}
}
};
OnLongClickListener longClickListener = new OnLongClickListener() {
/**
* We start the long click here but rely on the {@link NumberPickerButton}
* to inform us when the long click has ended.
*/
public boolean onLongClick(View v) {
/* The text view may still have focus so clear it's focus which will
* trigger the on focus changed and any typed values to be pulled.
*/
mText.clearFocus();
if (R.id.increment == v.getId()) {
mIncrement = true;
mHandler.post(mRunnable);
} else if (R.id.decrement == v.getId()) {
mDecrement = true;
mHandler.post(mRunnable);
}
return true;
}
};
InputFilter inputFilter = new NumberPickerInputFilter();
mNumberInputFilter = new NumberRangeKeyListener();
mIncrementButton = (NumberPickerButton) findViewById(R.id.increment);
mIncrementButton.setOnClickListener(clickListener);
mIncrementButton.setOnLongClickListener(longClickListener);
mIncrementButton.setNumberPicker(this);
mDecrementButton = (NumberPickerButton) findViewById(R.id.decrement);
mDecrementButton.setOnClickListener(clickListener);
mDecrementButton.setOnLongClickListener(longClickListener);
mDecrementButton.setNumberPicker(this);
mText = (EditText) findViewById(R.id.timepicker_input);
mText.setOnFocusChangeListener(focusListener);
mText.setFilters(new InputFilter[] {inputFilter});
mText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
if (!isEnabled()) {
setEnabled(false);
}
}
/**
* Set the enabled state of this view. The interpretation of the enabled
* state varies by subclass.
*
* @param enabled True if this view is enabled, false otherwise.
*/
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
mIncrementButton.setEnabled(enabled);
mDecrementButton.setEnabled(enabled);
mText.setEnabled(enabled);
}
/**
* Set the callback that indicates the number has been adjusted by the user.
* @param listener the callback, should not be null.
*/
public void setOnChangeListener(OnChangedListener listener) {
mListener = listener;
}
/**
* Set the formatter that will be used to format the number for presentation
* @param formatter the formatter object. If formatter is null, String.valueOf()
* will be used
*/
public void setFormatter(Formatter formatter) {
mFormatter = formatter;
}
/**
* Set the range of numbers allowed for the number picker. The current
* value will be automatically set to the start.
*
* @param start the start of the range (inclusive)
* @param end the end of the range (inclusive)
*/
public void setRange(int start, int end) {
setRange(start, end, null/*displayedValues*/);
}
/**
* Set the range of numbers allowed for the number picker. The current
* value will be automatically set to the start. Also provide a mapping
* for values used to display to the user.
*
* @param start the start of the range (inclusive)
* @param end the end of the range (inclusive)
* @param displayedValues the values displayed to the user.
*/
public void setRange(int start, int end, String[] displayedValues) {
mDisplayedValues = displayedValues;
mStart = start;
mEnd = end;
mCurrent = start;
updateView();
if (displayedValues != null) {
// Allow text entry rather than strictly numeric entry.
mText.setRawInputType(InputType.TYPE_CLASS_TEXT/* |
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS*/);
}
}
/**
* Set the current value for the number picker.
*
* @param current the current value the start of the range (inclusive)
* @throws IllegalArgumentException when current is not within the range
* of of the number picker
*/
public void setCurrent(int current) {
if (current < mStart || current > mEnd) {
throw new IllegalArgumentException(
"current should be >= start and <= end");
}
mCurrent = current;
updateView();
}
/**
* Sets the speed at which the numbers will scroll when the +/-
* buttons are longpressed
*
* @param speed The speed (in milliseconds) at which the numbers will scroll
* default 300ms
*/
public void setSpeed(long speed) {
mSpeed = speed;
}
private String formatNumber(int value) {
return (mFormatter != null)
? mFormatter.toString(value)
: String.valueOf(value);
}
/**
* Sets the current value of this NumberPicker, and sets mPrevious to the previous
* value. If current is greater than mEnd less than mStart, the value of mCurrent
* is wrapped around.
*
* Subclasses can override this to change the wrapping behavior
*
* @param current the new value of the NumberPicker
*/
protected void changeCurrent(int current) {
// Wrap around the values if we go past the start or end
if (current > mEnd) {
current = mStart;
} else if (current < mStart) {
current = mEnd;
}
mPrevious = mCurrent;
mCurrent = current;
notifyChange();
updateView();
}
/**
* Notifies the listener, if registered, of a change of the value of this
* NumberPicker.
*/
private void notifyChange() {
if (mListener != null) {
mListener.onChanged(this, mPrevious, mCurrent);
}
}
/**
* Updates the view of this NumberPicker. If displayValues were specified
* in {@link #setRange}, the string corresponding to the index specified by
* the current value will be returned. Otherwise, the formatter specified
* in will be used to format the number.
*/
private void updateView() {
/* If we don't have displayed values then use the
* current number else find the correct value in the
* displayed values for the current number.
*/
if (mDisplayedValues == null) {
mText.setText(formatNumber(mCurrent));
} else {
mText.setText(mDisplayedValues[mCurrent - mStart]);
}
mText.setSelection(mText.getText().length());
}
private void validateCurrentView(CharSequence str) {
int val = getSelectedPos(str.toString());
if ((val >= mStart) && (val <= mEnd)) {
if (mCurrent != val) {
mPrevious = mCurrent;
mCurrent = val;
notifyChange();
}
}
updateView();
}
private void validateInput(View v) {
String str = String.valueOf(((TextView) v).getText());
if ("".equals(str)) {
// Restore to the old value as we don't allow empty values
updateView();
} else {
// Check the new value and ensure it's in range
validateCurrentView(str);
}
}
/**
* @hide
*/
public void cancelIncrement() {
mIncrement = false;
}
/**
* @hide
*/
public void cancelDecrement() {
mDecrement = false;
}
private static final char[] DIGIT_CHARACTERS = new char[] {
'-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
};
private NumberPickerButton mIncrementButton;
private NumberPickerButton mDecrementButton;
private class NumberPickerInputFilter implements InputFilter {
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
if (mDisplayedValues == null) {
return mNumberInputFilter.filter(source, start, end, dest, dstart, dend);
}
CharSequence filtered = String.valueOf(source.subSequence(start, end));
String result = String.valueOf(dest.subSequence(0, dstart))
+ filtered
+ dest.subSequence(dend, dest.length());
String str = String.valueOf(result).toLowerCase();
for (String val : mDisplayedValues) {
val = val.toLowerCase();
if (val.startsWith(str)) {
return filtered;
}
}
return "";
}
}
private class NumberRangeKeyListener extends NumberKeyListener {
// XXX This doesn't allow for range limits when controlled by a
// soft input method!
public int getInputType() {
return InputType.TYPE_CLASS_NUMBER;
}
@Override
protected char[] getAcceptedChars() {
return DIGIT_CHARACTERS;
}
@Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
if (filtered == null) {
filtered = source.subSequence(start, end);
}
String result = String.valueOf(dest.subSequence(0, dstart))
+ filtered
+ dest.subSequence(dend, dest.length());
if ("".equals(result)) {
return result;
}
int val = getSelectedPos(result);
/* Ensure the user can't type in a value greater
* than the max allowed. We have to allow less than min
* as the user might want to delete some numbers
* and then type a new number.
*/
if (val > mEnd) {
return "";
} else {
return filtered;
}
}
}
private int getSelectedPos(String str) {
if (mDisplayedValues == null) {
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
/* Ignore as if it's not a number we don't care */
}
} else {
for (int i = 0; i < mDisplayedValues.length; i++) {
/* Don't force the user to type in jan when ja will do */
str = str.toLowerCase();
if (mDisplayedValues[i].toLowerCase().startsWith(str)) {
return mStart + i;
}
}
/* The user might have typed in a number into the month field i.e.
* 10 instead of OCT so support that too.
*/
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
/* Ignore as if it's not a number we don't care */
}
}
return mStart;
}
/**
* Returns the current value of the NumberPicker
* @return the current value.
*/
public int getCurrent() {
return mCurrent;
}
/**
* Returns the upper value of the range of the NumberPicker
* @return the uppper number of the range.
*/
protected int getEndRange() {
return mEnd;
}
/**
* Returns the lower value of the range of the NumberPicker
* @return the lower number of the range.
*/
protected int getBeginRange() {
return mStart;
}
}

View File

@@ -1,106 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.widgets;
/**
* User: serso
* Date: 9/18/11
* Time: 10:04 PM
*/
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.widget.ImageButton;
import org.solovyev.android.calculator.R;
/**
* This class exists purely to cancel long click events, that got
* started in NumberPicker
*/
class NumberPickerButton extends ImageButton {
private NumberPicker mNumberPicker;
public NumberPickerButton(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public NumberPickerButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NumberPickerButton(Context context) {
super(context);
}
public void setNumberPicker(NumberPicker picker) {
mNumberPicker = picker;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
cancelLongpressIfRequired(event);
return super.onTouchEvent(event);
}
@Override
public boolean onTrackballEvent(MotionEvent event) {
cancelLongpressIfRequired(event);
return super.onTrackballEvent(event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
|| (keyCode == KeyEvent.KEYCODE_ENTER)) {
cancelLongpress();
}
return super.onKeyUp(keyCode, event);
}
private void cancelLongpressIfRequired(MotionEvent event) {
if ((event.getAction() == MotionEvent.ACTION_CANCEL)
|| (event.getAction() == MotionEvent.ACTION_UP)) {
cancelLongpress();
}
}
private void cancelLongpress() {
if (R.id.increment == getId()) {
mNumberPicker.cancelIncrement();
} else if (R.id.decrement == getId()) {
mNumberPicker.cancelDecrement();
}
}
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (!hasWindowFocus) {
cancelLongpress();
}
}
}

View File

@@ -1,60 +0,0 @@
package org.solovyev.android.view.widgets;
import android.content.Context;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.utils.Converter;
import org.solovyev.common.utils.Interval;
import org.solovyev.common.utils.NumberValuer;
/**
* User: serso
* Date: 9/19/11
* Time: 4:26 PM
*/
public class NumberRangeSeekBar<T extends Number> extends AbstractRangeSeekBar<T> {
@NotNull
private final NumberType numberType;
public NumberRangeSeekBar(@NotNull Interval<T> boundaries, @Nullable Integer steps, Context context) throws IllegalArgumentException {
this(boundaries.getLeftBorder(), boundaries.getRightBorder(), steps, context);
}
/**
* Creates a new RangeSeekBar.
*
* @param minValue The minimum value of the selectable range.
* @param maxValue The maximum value of the selectable range.
* @param steps number of steps of range
* @param context parent context
* @throws IllegalArgumentException Will be thrown if min/max value types are not one of Long, Double, Integer, Float, Short, Byte or BigDecimal.
*/
public NumberRangeSeekBar(@NotNull T minValue, @NotNull T maxValue, @Nullable Integer steps, Context context) throws IllegalArgumentException {
super(minValue, maxValue, steps, context);
numberType = NumberType.fromNumber(minValue);
}
@NotNull
@Override
protected Converter<Double, T> getToTConverter() {
return new Converter<Double, T>() {
@NotNull
@Override
public T convert(@NotNull Double value) {
return (T) numberType.toNumber(value);
}
};
}
@NotNull
@Override
protected Converter<T, Double> getToDoubleConverter() {
return new NumberValuer<T>();
}
}

View File

@@ -1,68 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.widgets;
import org.jetbrains.annotations.NotNull;
import java.math.BigDecimal;
/**
* Utility enumeration used to convert between Numbers and doubles.
*
* @author Stephan Tittel (stephan.tittel@kom.tu-darmstadt.de)
*/
enum NumberType {
LONG(Long.class),
DOUBLE(Double.class),
INTEGER(Integer.class),
FLOAT(Float.class),
SHORT(Short.class),
BYTE(Byte.class),
BIG_DECIMAL(BigDecimal.class);
@NotNull
private final Class underlyingClass;
NumberType(@NotNull Class underlyingClass) {
this.underlyingClass = underlyingClass;
}
@NotNull
public static <E extends Number> NumberType fromNumber(E value) throws IllegalArgumentException {
for (NumberType numberType : NumberType.values()) {
if (numberType.underlyingClass.isInstance(value)) {
return numberType;
}
}
throw new IllegalArgumentException("Number class '" + value.getClass().getName() + "' is not supported");
}
public <T extends Number> T toNumber(double value) {
switch (this) {
case LONG:
return (T)new Long((long) value);
case DOUBLE:
return (T)new Double(value);
case INTEGER:
return (T)new Integer((int) value);
case FLOAT:
return (T)new Float((float) value);
case SHORT:
return (T)new Short((short) value);
case BYTE:
return (T)new Byte((byte) value);
case BIG_DECIMAL:
return (T)new BigDecimal(value);
}
throw new InstantiationError("can't convert " + this + " to a Number object");
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.widgets;
import android.content.SharedPreferences;
import android.os.Vibrator;
import android.view.View;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* User: serso
* Date: 10/26/11
* Time: 11:25 PM
*/
public class OnClickListenerVibrator implements View.OnClickListener {
private static final float VIBRATION_TIME_SCALE = 1.0f;
@NotNull
private VibratorContainer vibrator;
public OnClickListenerVibrator(@Nullable Vibrator vibrator,
@NotNull SharedPreferences preferences) {
this.vibrator = new VibratorContainer(vibrator, preferences, VIBRATION_TIME_SCALE);
}
@Override
public void onClick(View v) {
vibrator.vibrate();
}
}

View File

@@ -1,30 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.widgets;
import android.view.View;
import org.jetbrains.annotations.NotNull;
/**
* User: serso
* Date: 10/26/11
* Time: 10:55 PM
*/
public class OnClickListenerWrapper implements View.OnClickListener{
@NotNull
private final View.OnClickListener onClickListener;
public OnClickListenerWrapper(@NotNull View.OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
@Override
public void onClick(View v) {
this.onClick(v);
}
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.view.widgets;
import org.jetbrains.annotations.NotNull;
import org.solovyev.android.view.widgets.DragButton;
import org.solovyev.android.view.widgets.DragEvent;
import java.util.EventListener;
public interface OnDragListener extends EventListener{
/**
*
* @return 'true': if drag event has taken place (i.e. onDrag() method returned true) then click action will be suppresed
*/
boolean isSuppressOnClickEvent();
/**
* @param dragButton drag button object for which onDrag listener was set
* @param event drag event
*
* @return 'true' if drag event occurred, 'false' otherwise
*/
boolean onDrag(@NotNull DragButton dragButton, @NotNull DragEvent event);
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.widgets;
import org.jetbrains.annotations.NotNull;
/**
* User: serso
* Date: 10/26/11
* Time: 10:37 PM
*/
public class OnDragListenerWrapper implements OnDragListener {
@NotNull
private final OnDragListener onDragListener;
public OnDragListenerWrapper(@NotNull OnDragListener onDragListener) {
this.onDragListener = onDragListener;
}
@Override
public boolean isSuppressOnClickEvent() {
return this.onDragListener.isSuppressOnClickEvent();
}
@Override
public boolean onDrag(@NotNull DragButton dragButton, @NotNull DragEvent event) {
return this.onDragListener.onDrag(dragButton, event);
}
}

View File

@@ -1,305 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.view.widgets;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import android.view.MotionEvent;
import org.jetbrains.annotations.NotNull;
import org.solovyev.android.calculator.R;
import org.solovyev.common.NumberIntervalMapper;
import org.solovyev.common.utils.*;
import java.util.HashMap;
import java.util.Map;
public class SimpleOnDragListener implements OnDragListener, DragPreferencesChangeListener {
@NotNull
public static final Point2d axis = new Point2d(0, 1);
@NotNull
private DragProcessor dragProcessor;
@NotNull
private Preferences preferences;
public SimpleOnDragListener(@NotNull Preferences preferences) {
this.preferences = preferences;
}
public SimpleOnDragListener(@NotNull DragProcessor dragProcessor, @NotNull Preferences preferences) {
this.dragProcessor = dragProcessor;
this.preferences = preferences;
}
@Override
public boolean onDrag(@NotNull DragButton dragButton, @NotNull DragEvent event) {
boolean result = false;
logDragEvent(dragButton, event);
final Point2d startPoint = event.getStartPoint();
final MotionEvent motionEvent = event.getMotionEvent();
// init end point
final Point2d endPoint = new Point2d(motionEvent.getX(), motionEvent.getY());
final float distance = MathUtils.getDistance(startPoint, endPoint);
final MutableObject<Boolean> right = new MutableObject<Boolean>();
final double angle = Math.toDegrees(MathUtils.getAngle(startPoint, MathUtils.sum(startPoint, axis), endPoint, right));
Log.d(String.valueOf(dragButton.getId()), "Angle: " + angle);
Log.d(String.valueOf(dragButton.getId()), "Is right?: " + right.getObject());
final double duration = motionEvent.getEventTime() - motionEvent.getDownTime();
final Preference distancePreferences = preferences.getPreferencesMap().get(PreferenceType.distance);
final Preference anglePreferences = preferences.getPreferencesMap().get(PreferenceType.angle);
DragDirection direction = null;
for (Map.Entry<DragDirection, DragPreference> directionEntry : distancePreferences.getDirectionPreferences().entrySet()) {
Log.d(String.valueOf(dragButton.getId()), "Drag direction: " + directionEntry.getKey());
Log.d(String.valueOf(dragButton.getId()), "Trying direction interval: " + directionEntry.getValue().getInterval());
if (isInInterval(directionEntry.getValue().getInterval(), distance)) {
final DragPreference anglePreference = anglePreferences.getDirectionPreferences().get(directionEntry.getKey());
Log.d(String.valueOf(dragButton.getId()), "Trying angle interval: " + anglePreference.getInterval());
if (directionEntry.getKey() == DragDirection.left && right.getObject()) {
} else if (directionEntry.getKey() == DragDirection.right && !right.getObject()) {
} else {
if (isInInterval(anglePreference.getInterval(), (float) angle)) {
direction = directionEntry.getKey();
Log.d(String.valueOf(dragButton.getId()), "MATCH! Direction: " + direction);
break;
}
}
}
}
if (direction != null) {
final Preference durationPreferences = preferences.getPreferencesMap().get(PreferenceType.duration);
final DragPreference durationDragPreferences = durationPreferences.getDirectionPreferences().get(direction);
Log.d(String.valueOf(dragButton.getId()), "Trying time interval: " + durationDragPreferences.getInterval());
if (isInInterval(durationDragPreferences.getInterval(), (float) duration)) {
Log.d(String.valueOf(dragButton.getId()), "MATCH!");
result = dragProcessor.processDragEvent(direction, dragButton, startPoint, motionEvent);
}
}
return result;
}
private boolean isInInterval(@NotNull Interval<Float> interval, float value) {
return interval.getLeftBorder() - MathUtils.MIN_AMOUNT <= value && value <= interval.getRightBorder() + MathUtils.MIN_AMOUNT;
}
@Override
public boolean isSuppressOnClickEvent() {
return true;
}
private void logDragEvent(@NotNull DragButton dragButton, @NotNull DragEvent event) {
final Point2d startPoint = event.getStartPoint();
final MotionEvent motionEvent = event.getMotionEvent();
final Point2d endPoint = new Point2d(motionEvent.getX(), motionEvent.getY());
Log.d(String.valueOf(dragButton.getId()), "Start point: " + startPoint + ", End point: " + endPoint);
Log.d(String.valueOf(dragButton.getId()), "Distance: " + MathUtils.getDistance(startPoint, endPoint));
final MutableObject<Boolean> right = new MutableObject<Boolean>();
Log.d(String.valueOf(dragButton.getId()), "Angle: " + Math.toDegrees(MathUtils.getAngle(startPoint, MathUtils.sum(startPoint, axis), endPoint, right)));
Log.d(String.valueOf(dragButton.getId()), "Is right angle? " + right);
Log.d(String.valueOf(dragButton.getId()), "Axis: " + axis + " Vector: " + MathUtils.subtract(endPoint, startPoint));
Log.d(String.valueOf(dragButton.getId()), "Total time: " + (motionEvent.getEventTime() - motionEvent.getDownTime()) + " ms");
}
@NotNull
public DragProcessor getDragProcessor() {
return dragProcessor;
}
public void setDragProcessor(@NotNull DragProcessor dragProcessor) {
this.dragProcessor = dragProcessor;
}
@Override
public void onDragPreferencesChange(@NotNull Preferences preferences) {
this.preferences = preferences;
}
public interface DragProcessor {
boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent);
}
// todo serso: currently we do not use direction
public static String getPreferenceId(@NotNull PreferenceType preferenceType, @NotNull DragDirection direction) {
return "org.solovyev.android.calculator.DragButtonCalibrationActivity" + "_" + preferenceType.name() /*+ "_" + direction.name()*/;
}
@NotNull
public static Preferences getPreferences(@NotNull final SharedPreferences preferences, @NotNull Context context) {
final Mapper<Interval<Float>> mapper = new NumberIntervalMapper<Float>(Float.class);
final Preferences result = new Preferences();
for (PreferenceType preferenceType : PreferenceType.values()) {
for (DragDirection dragDirection : DragDirection.values()) {
final String preferenceId = getPreferenceId(preferenceType, dragDirection);
final String defaultValue;
switch (preferenceType) {
case angle:
defaultValue = context.getResources().getString(R.string.p_drag_angle);
break;
case distance:
defaultValue = context.getResources().getString(R.string.p_drag_distance);
break;
case duration:
defaultValue = context.getResources().getString(R.string.p_drag_duration);
break;
default:
defaultValue = null;
Log.e(SimpleOnDragListener.class.getName(), "New preference type added: default preferences should be defined. Preference id: " + preferenceId);
}
final String value = preferences.getString(preferenceId, defaultValue);
if (defaultValue != null) {
final Interval<Float> intervalPref = mapper.parseValue(value);
assert intervalPref != null;
transformInterval(preferenceType, dragDirection, intervalPref);
Log.d(SimpleOnDragListener.class.getName(), "Preference loaded for " + dragDirection +". Id: " + preferenceId + ", value: " + intervalPref.toString());
final DragPreference directionPreference = new DragPreference(dragDirection, intervalPref);
Preference preference = result.getPreferencesMap().get(preferenceType);
if (preference == null) {
preference = new Preference(preferenceType);
result.getPreferencesMap().put(preferenceType, preference);
}
preference.getDirectionPreferences().put(dragDirection, directionPreference);
}
}
}
return result;
}
@NotNull
public static Interval<Float> transformInterval(@NotNull PreferenceType preferenceType, @NotNull DragDirection dragDirection, @NotNull Interval<Float> interval) {
if (preferenceType == PreferenceType.angle) {
final Float leftBorder = interval.getLeftBorder();
final Float rightBorder = interval.getRightBorder();
if (dragDirection == DragDirection.up) {
interval.setLeftBorder(180f - rightBorder);
interval.setRightBorder(180f - leftBorder);
} else if (dragDirection == DragDirection.left ) {
interval.setLeftBorder(90f - rightBorder);
interval.setRightBorder(90f + rightBorder);
} else if ( dragDirection == DragDirection.right ) {
interval.setLeftBorder(90f - rightBorder);
interval.setRightBorder(90f + rightBorder);
}
}
return interval;
}
public static enum PreferenceType {
angle,
distance,
duration
}
public static class DragPreference {
@NotNull
private DragDirection direction;
@NotNull
private Interval<Float> interval;
public DragPreference(@NotNull DragDirection direction, @NotNull Interval<Float> interval) {
this.direction = direction;
this.interval = interval;
}
@NotNull
public DragDirection getDirection() {
return direction;
}
public void setDirection(@NotNull DragDirection direction) {
this.direction = direction;
}
@NotNull
public Interval<Float> getInterval() {
return interval;
}
public void setInterval(@NotNull Interval<Float> interval) {
this.interval = interval;
}
}
public static class Preference {
@NotNull
private PreferenceType preferenceType;
@NotNull
private Map<DragDirection, DragPreference> directionPreferences = new HashMap<DragDirection, DragPreference>();
public Preference(@NotNull PreferenceType preferenceType) {
this.preferenceType = preferenceType;
}
@NotNull
public PreferenceType getPreferenceType() {
return preferenceType;
}
public void setPreferenceType(@NotNull PreferenceType preferenceType) {
this.preferenceType = preferenceType;
}
@NotNull
public Map<DragDirection, DragPreference> getDirectionPreferences() {
return directionPreferences;
}
public void setDirectionPreferences(@NotNull Map<DragDirection, DragPreference> directionPreferences) {
this.directionPreferences = directionPreferences;
}
}
public static class Preferences {
private final Map<PreferenceType, Preference> preferencesMap = new HashMap<PreferenceType, Preference>();
public Map<PreferenceType, Preference> getPreferencesMap() {
return preferencesMap;
}
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view.widgets;
import android.content.SharedPreferences;
import android.os.Vibrator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.NumberMapper;
/**
* User: serso
* Date: 10/26/11
* Time: 11:40 PM
*/
public class VibratorContainer implements SharedPreferences.OnSharedPreferenceChangeListener {
public static final String HAPTIC_FEEDBACK_P_KEY = "org.solovyev.android.calculator.CalculatorModel_haptic_feedback";
public static final boolean HAPTIC_FEEDBACK_DEFAULT = false;
public static final String HAPTIC_FEEDBACK_DURATION_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_calc_haptic_feedback_duration_key";
private final static NumberMapper<Long> mapper = new NumberMapper<Long>(Long.class);
private static final long defaultVibrationTime = 100;
private final float vibrationTimeScale;
@Nullable
private final Vibrator vibrator;
private long time = 0;
public VibratorContainer(@Nullable Vibrator vibrator, @NotNull SharedPreferences preferences, float vibrationTimeScale) {
this.vibrator = vibrator;
this.vibrationTimeScale = vibrationTimeScale;
preferences.registerOnSharedPreferenceChangeListener(this);
onSharedPreferenceChanged(preferences, null);
}
public void vibrate() {
if (time > 0 && vibrator != null) {
vibrator.vibrate(time);
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, @Nullable String key) {
if (preferences.getBoolean(HAPTIC_FEEDBACK_P_KEY, VibratorContainer.HAPTIC_FEEDBACK_DEFAULT)) {
//noinspection ConstantConditions
this.time = getScaledValue(mapper.parseValue(preferences.getString(HAPTIC_FEEDBACK_DURATION_P_KEY, mapper.formatValue(getScaledValue(defaultVibrationTime)))));
} else {
this.time = 0;
}
}
private long getScaledValue(long vibrationTime) {
return (long) (vibrationTime * vibrationTimeScale);
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.common;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.utils.*;
import java.util.Arrays;
import java.util.List;
/**
* User: serso
* Date: 9/21/11
* Time: 12:02 AM
*/
public abstract class AbstractIntervalMapper<T> implements Mapper<Interval<T>> {
@Override
public String formatValue(@Nullable Interval<T> interval) throws IllegalArgumentException {
if (interval != null) {
return CollectionsUtils.formatValue(Arrays.asList(interval.getLeftBorder(), interval.getRightBorder()), ";", getFormatter());
} else {
return null;
}
}
@NotNull
protected abstract Formatter<T> getFormatter();
@Override
public Interval<T> parseValue(@Nullable String s) throws IllegalArgumentException {
//Log.d(AbstractIntervalMapper.class.getName(), "Parsing: " + s);
final List<T> list = CollectionsUtils.split(s, ";", getParser());
assert list.size() == 2;
return newInstance(list.get(0), list.get(1));
}
@NotNull
protected abstract Interval<T> newInstance(@Nullable T left, @Nullable T right);
@NotNull
protected abstract Parser<T> getParser();
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.common;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.math.ValueOfFormatter;
import org.solovyev.common.utils.Formatter;
import org.solovyev.common.utils.Mapper;
/**
* User: serso
* Date: 9/26/11
* Time: 11:27 PM
*/
public class BooleanMapper implements Mapper<Boolean>{
@NotNull
private final Formatter<Boolean> formatter = new ValueOfFormatter<Boolean>();
@Override
public String formatValue(@Nullable Boolean value) throws IllegalArgumentException {
return formatter.formatValue(value);
}
@Override
public Boolean parseValue(@Nullable String value) throws IllegalArgumentException {
return value == null ? null : Boolean.valueOf(value);
}
}

View File

@@ -1,44 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.common;
import org.jetbrains.annotations.NotNull;
import org.solovyev.common.utils.Formatter;
import org.solovyev.common.utils.Mapper;
import org.solovyev.common.utils.Parser;
/**
* User: serso
* Date: 9/26/11
* Time: 10:45 PM
*/
public abstract class GenericIntervalMapper<T> extends AbstractIntervalMapper<T> {
@NotNull
private final Mapper<T> mapper;
public GenericIntervalMapper(@NotNull Mapper<T> mapper) {
this.mapper = mapper;
}
@NotNull
@Override
protected Formatter<T> getFormatter() {
return mapper;
}
@NotNull
@Override
protected Parser<T> getParser() {
return mapper;
}
@NotNull
public Mapper<T> getMapper() {
return mapper;
}
}

View File

@@ -1,30 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.common;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.utils.Interval;
import org.solovyev.common.utils.NumberInterval;
/**
* User: serso
* Date: 9/20/11
* Time: 11:56 PM
*/
public class NumberIntervalMapper<T extends Number> extends GenericIntervalMapper<T> {
public NumberIntervalMapper(@NotNull Class<T> clazz) {
super(new NumberMapper<T>(clazz));
}
@NotNull
@Override
protected Interval<T> newInstance(@Nullable T left, @Nullable T right) {
return new NumberInterval<T>(left, right);
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.common;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.math.ValueOfFormatter;
import org.solovyev.common.utils.Formatter;
import org.solovyev.common.utils.Mapper;
import org.solovyev.common.utils.Parser;
/**
* User: serso
* Date: 9/26/11
* Time: 11:10 PM
*/
public class NumberMapper<T extends Number> implements Mapper<T>{
@NotNull
private final Formatter<T> formatter = new ValueOfFormatter<T>();
@NotNull
private final Parser<T> parser;
public NumberMapper(@NotNull Class<T> clazz) {
this.parser = new NumberParser<T>(clazz);
}
@Override
public String formatValue(@Nullable T value) throws IllegalArgumentException {
return formatter.formatValue(value);
}
@Override
public T parseValue(@Nullable String value) throws IllegalArgumentException {
return this.parser.parseValue(value);
}
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.common;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.utils.Parser;
/**
* User: serso
* Date: 9/26/11
* Time: 11:07 PM
*/
public class NumberParser<T extends Number> implements Parser<T> {
@NotNull
private final Class<T> clazz;
public NumberParser(@NotNull Class<T> clazz) {
this.clazz = clazz;
}
@Override
public T parseValue(@Nullable String value) throws IllegalArgumentException {
T result;
if (value != null) {
if (this.clazz.equals(Integer.class)) {
result = (T) Integer.valueOf(value);
} else if (this.clazz.equals(Float.class)) {
result = (T) Float.valueOf(value);
} else if (this.clazz.equals(Long.class)) {
result = (T) Long.valueOf(value);
} else {
throw new UnsupportedOperationException(this.clazz + " is not supported!");
}
} else {
result = null;
}
return result;
}
}

View File

@@ -1,29 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.common;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.utils.Equalizer;
/**
* User: serso
* Date: 12/17/11
* Time: 11:11 PM
*/
public enum SameEqualizer implements Equalizer {
instance;
private SameEqualizer() {
}
@Override
public boolean equals(@Nullable Object first, @Nullable Object second) {
return first == second;
}
}

View File

@@ -1,68 +0,0 @@
package org.solovyev.common.math;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* User: serso
* Date: 9/20/11
* Time: 5:42 PM
*/
public class DiscreteNormalizer implements Normalizer {
@NotNull
private final Normalizer normalizer;
private final double min;
private final double step;
public DiscreteNormalizer(double min, double max, int steps) {
this(min, max, steps, null);
}
public DiscreteNormalizer(double min, double max, int steps, @Nullable Normalizer normalizer) {
assert min <= max;
assert steps > 1;
if (normalizer != null) {
this.normalizer = normalizer;
} else {
this.normalizer = new LinearNormalizer(min, max);
}
this.step = this.normalizer.normalize((max - min) / (steps - 1));
this.min = this.normalizer.normalize(min);
}
public DiscreteNormalizer(double min, double max, double step) {
assert min <= max;
assert step > 0;
this.normalizer = new LinearNormalizer(min, max);
this.step = normalizer.normalize(step);
this.min = normalizer.normalize(min);
}
@Override
public double normalize(double value) {
double normalizedValue = normalizer.normalize(value);
double result = min;
while (true) {
if ( result + step > normalizedValue ) {
break;
} else {
result += step;
}
}
return result;
}
@Override
public double denormalize(double value) {
return normalizer.denormalize(value);
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.common.math;
/**
* User: serso
* Date: 9/19/11
* Time: 9:31 PM
*/
public class LinearNormalizer implements Normalizer {
private final double min;
private final double max;
public LinearNormalizer(double min, double max) {
this.min = min;
this.max = max;
}
@Override
public double normalize(double value){
if ((max - min) != 0d) {
return (value - min) / (max - min);
} else {
return 1d;
}
}
@Override
public double denormalize(double value){
return min + value * (max - min);
}
}

View File

@@ -1,13 +0,0 @@
package org.solovyev.common.math;
/**
* User: serso
* Date: 9/20/11
* Time: 5:30 PM
*/
public interface Normalizer {
double normalize(double value);
double denormalize(double value);
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.common.math;
import org.jetbrains.annotations.Nullable;
import org.solovyev.common.utils.Formatter;
/**
* User: serso
* Date: 9/20/11
* Time: 10:52 PM
*/
public class ValueOfFormatter<T> implements Formatter<T>{
private final boolean processNulls;
public ValueOfFormatter() {
this(false);
}
public ValueOfFormatter(boolean processNulls) {
this.processNulls = processNulls;
}
@Override
public String formatValue(@Nullable T t) throws IllegalArgumentException {
return t == null ? (processNulls ? String.valueOf(t) : null) : String.valueOf(t);
}
}