/* * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * <kool.name at gmail.com> wrote this file. As long as you retain this notice you * can do whatever you want with this stuff. If we meet some day, and you think * this stuff is worth it, you can buy me a beer in return. Justin "kool.name" Lee * ---------------------------------------------------------------------------- */ package net.toload.main.hd.limesettings; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; import android.preference.DialogPreference; import android.util.AttributeSet; import android.util.Log; import android.widget.Toast; public class MultiListPreference extends DialogPreference { /** Tag for logging! */ public static final boolean DEBUG = false; public static final String TAG = "MultiListPreference"; /** How the "choices" will be delimeted. */ public static final String CHOICE_DELIMITER = ";"; /** Truth regex thing. */ public static final String TRUTH_REGEX = "^(?i:t(?:rue)?)"; /** "Using default." message incase of dialog error. */ public static final String USING_DEFAULT = "Using default."; /** Contains the boolean state of each entry. */ private boolean[] state = null; /** Contains textual representations of the entries. */ private CharSequence[] entry = null; /** Contains the default value, if any. */ private String defaultValue = null; /** Parse character sequence array into boolean array using regex. @param state Character sequence array to parse. @return Boolean array or null. */ private static boolean[] cs2b(CharSequence[] state) { if(DEBUG) Log.d(TAG, "cs2b()"); boolean[] out = null; if (state != null) { out = new boolean[state.length]; for (int e = 0; e < state.length; e++) { out[e] = state[e].toString().matches(TRUTH_REGEX); } } return (out); } /** Parse delimited numbers into a boolean array. @param state The states given here will be true. @param size The array size. @return Boolean array of given size or null. */ private static boolean[] ds2b(CharSequence state, int size) { if(DEBUG) Log.d(TAG, "ds2b(): " + state + " (" + size + ")"); boolean[] out = null; if (state != null) { out = new boolean[size]; String[] s = state.toString().split(CHOICE_DELIMITER); for (int e = 0; e < s.length; e++) { int index = Integer.parseInt(s[e]); if (index < out.length) { out[index] = true; } else { out = null; break; } } } return (out); } /** Turn boolean array into delimited numbers. @param state State array. @return Delimited number thing or null. */ private static CharSequence b2ds(boolean[] state) { if(DEBUG) Log.d(TAG, "b2ds()"); String out = null; if (state != null) { for (int e = 0; e < state.length; e++) { if (state[e]) { if (out == null) { out = "" + e; } else { out += CHOICE_DELIMITER + e; } } } if (out == null) { out = ""; } } return (out); } /** Set the entries to something new. @param entry New entry array. @return Success or failure flag. */ public boolean setEntries(CharSequence[] entry) { if(DEBUG) Log.d(TAG, "setEntries()"); boolean updated = false; if (this.entry == null) { if (this.state == null) { updated = true; } else if (entry.length == this.state.length) { updated = true; } } else if (entry.length == this.entry.length) { updated = true; } if (updated) { this.entry = entry; } return (updated); } /** Fetch a copy of the current entry array. @return Current entry array clone. */ public CharSequence[] getEntries() { if(DEBUG) Log.d(TAG, "getEntries()"); return (this.entry.clone()); } /** Set the state to something new. @param state New state array. @return Success or failure flag. */ public boolean setValue(boolean[] state) { if(DEBUG) Log.d(TAG, "setValue()"); boolean updated = false; if (this.state == null) { if (this.entry == null) { updated = true; } else if (state.length == this.entry.length) { updated = true; } } else if (state.length == this.state.length) { updated = true; } if (updated) { this.state = state; } return (updated); } /** Set the state to something new, state must be previously initialized. @param state New state, given by delimeted index. @return Success or failure flag. */ public boolean setValue(CharSequence state) { if(DEBUG) Log.d(TAG, "setValue(): " + state); boolean updated = false; if (this.state != null) { boolean[] newState = ds2b(state, this.state.length); if (newState != null) { this.state = newState; updated = true; } } return (updated); } /** Fetch a copy of the current state array. @return Current state array clone. */ public boolean[] getValue() { if(DEBUG) Log.d(TAG, "getValue()"); return (this.state.clone()); } public MultiListPreference(Context context, AttributeSet attrs) { super(context, attrs); if(DEBUG) Log.d(TAG, "MultiListPreference()"); TypedArray in = context.obtainStyledAttributes( attrs, new int[]{ // "android:entries", just like ListPreference android.R.attr.entries, // "android:entryValues", these are defaults ONLY android.R.attr.entryValues, // "android:defaultValue" android.R.attr.defaultValue } ); this.entry = in.getTextArray(0); this.defaultValue = in.getString(2); if (this.entry == null) { if(DEBUG) Log.d(TAG, "MultiListPreference(): Could not restore entry."); } else { if(DEBUG) Log.d(TAG, "MultiListPreference(): Entry from defaults."); if (this.defaultValue != null) { this.state = ds2b(this.defaultValue, this.entry.length); } if (this.state == null) { this.state = cs2b(in.getTextArray(1)); if (this.state == null) { if(DEBUG) Log.d(TAG, "MultiListPreference(): Could not restore state."); } else { if(DEBUG) Log.d(TAG, "MultiListPreference(): Restored state from entryValues."); } } else { if(DEBUG) Log.d(TAG, "MultiListPreference(): Restored state from defaultValue."); } } in.recycle(); } @Override // here we set the multichoiceitem content public void onPrepareDialogBuilder(AlertDialog.Builder builder) { if(DEBUG) Log.d(TAG, "onPrepareDialogBuilder()"); // try to restore state from persisted value if (this.entry != null || this.state != null) { int size = ( (this.entry == null) ? this.state.length : this.entry.length ); boolean[] persistedState = ds2b(this.getPersistedString(null), size); if (persistedState != null) { this.state = persistedState; if(DEBUG) Log.d(TAG, "onPrepareDialogBuilder(): Persisted state restored."); } } // build the dialog builder .setCancelable(false) .setMultiChoiceItems( this.entry, // entries pulled from XML this.state, new DialogInterface.OnMultiChoiceClickListener() { // make a "convenience" pointer, cause I'm lazy private final MultiListPreference that = MultiListPreference.this; @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { if(DEBUG) Log.d(TAG, "listItem_" + which + ".onClick(): " + isChecked); that.state[which] = isChecked; } } ) ; } @Override // called when OK (true) or Cancel (false) are pushed public void onDialogClosed(boolean positiveResult) { if(DEBUG) Log.d(TAG, "onDialogClosed(): " + positiveResult); if (positiveResult) { // only commit if my change listener says so if (this.callChangeListener(this.state)) { String out = (String) b2ds(this.state); // check if the value is OK if (out == null || out == "") { Toast.makeText( this.getContext(), USING_DEFAULT, Toast.LENGTH_SHORT ).show(); out = ( (this.defaultValue == null) ? "0" // XXX: lol, hax : this.defaultValue ); } if(DEBUG) Log.d(TAG, "onDialogClosed(): Saving: " + out); if (this.persistString(out)) { this.notifyChanged(); } else { if(DEBUG) Log.d(TAG, "onDialogClosed(): Saving failed."); } } } } }