diff options
| author | Jules Aguillon | 2024-01-13 20:59:05 +0100 |
|---|---|---|
| committer | Jules Aguillon | 2024-01-13 20:59:05 +0100 |
| commit | eddf9c6c117449012e2aece5776694467e3483f0 (patch) | |
| tree | 0f503419b7beffc15f6f0903d95e68d1c4bdddd1 /srcs/juloo.keyboard2/prefs | |
| parent | 148f3dfc052bc6a4161073e59505bd547ab347c3 (diff) | |
| download | unexpected-keyboard-eddf9c6c117449012e2aece5776694467e3483f0.tar.gz unexpected-keyboard-eddf9c6c117449012e2aece5776694467e3483f0.zip | |
Refactor: New namespace for preference classes
Diffstat (limited to 'srcs/juloo.keyboard2/prefs')
| -rw-r--r-- | srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java | 70 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java | 177 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/prefs/IntSlideBarPreference.java | 120 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/prefs/LayoutsPreference.java | 302 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/prefs/ListGroupPreference.java | 294 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/prefs/SlideBarPreference.java | 131 |
6 files changed, 1094 insertions, 0 deletions
diff --git a/srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java b/srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java new file mode 100644 index 0000000..9d8395f --- /dev/null +++ b/srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java @@ -0,0 +1,70 @@ +package juloo.keyboard2.prefs; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.preference.Preference; +import android.preference.PreferenceCategory; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import juloo.keyboard2.*; +import org.json.JSONArray; +import org.json.JSONException; + +/** Allows to enter custom keys to be added to the keyboard. This shows up at + the top of the "Add keys to the keyboard" option. */ +public class CustomExtraKeysPreference extends ListGroupPreference<String> +{ + /** This pref stores a list of strings encoded as JSON. */ + static final String KEY = "custom_extra_keys"; + static final ListGroupPreference.Serializer<String> SERIALIZER = + new ListGroupPreference.StringSerializer(); + + public CustomExtraKeysPreference(Context context, AttributeSet attrs) + { + super(context, attrs); + setKey(KEY); + } + + public static Map<KeyValue, KeyboardData.PreferredPos> get(SharedPreferences prefs) + { + Map<KeyValue, KeyboardData.PreferredPos> kvs = + new HashMap<KeyValue, KeyboardData.PreferredPos>(); + List<String> key_names = load_from_preferences(KEY, prefs, null, SERIALIZER); + if (key_names != null) + { + for (String key_name : key_names) + kvs.put(KeyValue.makeStringKey(key_name), KeyboardData.PreferredPos.DEFAULT); + } + return kvs; + } + + String label_of_value(String value, int i) { return value; } + + @Override + void select(final SelectionCallback<String> callback) + { + new AlertDialog.Builder(getContext()) + .setView(R.layout.dialog_edit_text) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener(){ + public void onClick(DialogInterface dialog, int which) + { + EditText input = (EditText)((AlertDialog)dialog).findViewById(R.id.text); + final String k = input.getText().toString(); + if (!k.equals("")) + callback.select(k); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + @Override + Serializer<String> get_serializer() { return SERIALIZER; } +} diff --git a/srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java b/srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java new file mode 100644 index 0000000..47ca535 --- /dev/null +++ b/srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java @@ -0,0 +1,177 @@ +package juloo.keyboard2.prefs; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.preference.CheckBoxPreference; +import android.preference.PreferenceCategory; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import juloo.keyboard2.*; + +/** This class implements the "extra keys" preference but also defines the + possible extra keys. */ +public class ExtraKeysPreference extends PreferenceCategory +{ + /** Array of the keys that can be selected. */ + public static String[] extra_keys = new String[] + { + "alt", + "meta", + "voice_typing", + "accent_aigu", + "accent_grave", + "accent_double_aigu", + "accent_dot_above", + "accent_circonflexe", + "accent_tilde", + "accent_cedille", + "accent_trema", + "accent_ring", + "accent_caron", + "accent_macron", + "accent_ogonek", + "accent_breve", + "accent_slash", + "accent_bar", + "accent_dot_below", + "accent_hook_above", + "accent_horn", + "€", + "ß", + "£", + "§", + "†", + "ª", + "º", + "page_up", + "page_down", + "home", + "end", + "switch_greekmath", + "capslock", + "copy", + "paste", + "cut", + "selectAll", + "shareText", + "pasteAsPlainText", + "undo", + "redo", + "superscript", + "subscript", + }; + + /** Whether an extra key is enabled by default. */ + public static boolean default_checked(String name) + { + switch (name) + { + case "voice_typing": + return true; + default: + return false; + } + } + + /** Text that describe a key. Might be null. */ + static String key_description(Resources res, String name) + { + int id = 0; + switch (name) + { + case "capslock": id = R.string.key_descr_capslock; break; + case "switch_greekmath": id = R.string.key_descr_switch_greekmath; break; + case "voice_typing": id = R.string.key_descr_voice_typing; break; + case "copy": id = R.string.key_descr_copy; break; + case "paste": id = R.string.key_descr_paste; break; + case "cut": id = R.string.key_descr_cut; break; + case "selectAll": id = R.string.key_descr_selectAll; break; + case "shareText": id = R.string.key_descr_shareText; break; + case "pasteAsPlainText": id = R.string.key_descr_pasteAsPlainText; break; + case "undo": id = R.string.key_descr_undo; break; + case "redo": id = R.string.key_descr_redo; break; + case "ª": id = R.string.key_descr_ª; break; + case "º": id = R.string.key_descr_º; break; + case "superscript": id = R.string.key_descr_superscript; break; + case "subscript": id = R.string.key_descr_subscript; break; + case "page_up": id = R.string.key_descr_page_up; break; + case "page_down": id = R.string.key_descr_page_down; break; + case "home": id = R.string.key_descr_home; break; + case "end": id = R.string.key_descr_end; break; + } + if (id == 0) + return null; + return res.getString(id); + } + + /** Get the set of enabled extra keys. */ + public static Map<KeyValue, KeyboardData.PreferredPos> get_extra_keys(SharedPreferences prefs) + { + Map<KeyValue, KeyboardData.PreferredPos> ks = + new HashMap<KeyValue, KeyboardData.PreferredPos>(); + for (String key_name : extra_keys) + { + if (prefs.getBoolean(pref_key_of_key_name(key_name), + default_checked(key_name))) + ks.put(KeyValue.getKeyByName(key_name), KeyboardData.PreferredPos.DEFAULT); + } + return ks; + } + + boolean _attached = false; /** Whether it has already been attached. */ + + public ExtraKeysPreference(Context context, AttributeSet attrs) + { + super(context, attrs); + setOrderingAsAdded(true); + } + + @Override + protected void onAttachedToActivity() + { + if (_attached) + return; + _attached = true; + for (String key_name : extra_keys) + addPreference(new ExtraKeyCheckBoxPreference(getContext(), key_name, + default_checked(key_name))); + } + + public static String pref_key_of_key_name(String key_name) + { + return "extra_key_" + key_name; + } + + static class ExtraKeyCheckBoxPreference extends CheckBoxPreference + { + boolean _key_font; + + public ExtraKeyCheckBoxPreference(Context ctx, String key_name, + boolean default_checked) + { + super(ctx); + KeyValue kv = KeyValue.getKeyByName(key_name); + String title = kv.getString(); + String descr = key_description(ctx.getResources(), key_name); + if (descr != null) + title += " (" + descr + ")"; + setKey(pref_key_of_key_name(key_name)); + setDefaultValue(default_checked); + setTitle(title); + _key_font = kv.hasFlags(KeyValue.FLAG_KEY_FONT); + } + + @Override + protected void onBindView(View view) + { + super.onBindView(view); + TextView title = (TextView)view.findViewById(android.R.id.title); + title.setTypeface(_key_font ? Theme.getKeyFont(getContext()) : null); + } + } +} diff --git a/srcs/juloo.keyboard2/prefs/IntSlideBarPreference.java b/srcs/juloo.keyboard2/prefs/IntSlideBarPreference.java new file mode 100644 index 0000000..bb6bf7d --- /dev/null +++ b/srcs/juloo.keyboard2/prefs/IntSlideBarPreference.java @@ -0,0 +1,120 @@ +package juloo.keyboard2.prefs; + +import android.content.Context; +import android.content.res.TypedArray; +import android.preference.DialogPreference; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.SeekBar; + +/* + ** IntSlideBarPreference + ** - + ** Open a dialog showing a seekbar + ** - + ** xml attrs: + ** android:defaultValue Default value (int) + ** min min value (int) + ** max max value (int) + ** - + ** Summary field allow to show the current value using %s flag + */ +public class IntSlideBarPreference extends DialogPreference + implements SeekBar.OnSeekBarChangeListener +{ + private LinearLayout _layout; + private TextView _textView; + private SeekBar _seekBar; + + private int _min; + + private String _initialSummary; + + public IntSlideBarPreference(Context context, AttributeSet attrs) + { + super(context, attrs); + _initialSummary = getSummary().toString(); + _textView = new TextView(context); + _textView.setPadding(48, 40, 48, 40); + _seekBar = new SeekBar(context); + _seekBar.setOnSeekBarChangeListener(this); + _min = attrs.getAttributeIntValue(null, "min", 0); + int max = attrs.getAttributeIntValue(null, "max", 0); + _seekBar.setMax(max - _min); + _layout = new LinearLayout(getContext()); + _layout.setOrientation(LinearLayout.VERTICAL); + _layout.addView(_textView); + _layout.addView(_seekBar); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) + { + updateText(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) + { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) + { + } + + @Override + protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) + { + int value; + + if (restorePersistedValue) + { + value = getPersistedInt(_min); + } + else + { + value = (Integer)defaultValue; + persistInt(value); + } + _seekBar.setProgress(value - _min); + updateText(); + } + + @Override + protected Object onGetDefaultValue(TypedArray a, int index) + { + return (a.getInt(index, _min)); + } + + @Override + protected void onDialogClosed(boolean positiveResult) + { + if (positiveResult) + persistInt(_seekBar.getProgress() + _min); + else + _seekBar.setProgress(getPersistedInt(_min) - _min); + + updateText(); + } + + protected View onCreateDialogView() + { + ViewGroup parent = (ViewGroup)_layout.getParent(); + + if (parent != null) + parent.removeView(_layout); + return (_layout); + } + + private void updateText() + { + String f = String.format(_initialSummary, _seekBar.getProgress() + _min); + + _textView.setText(f); + setSummary(f); + } +} diff --git a/srcs/juloo.keyboard2/prefs/LayoutsPreference.java b/srcs/juloo.keyboard2/prefs/LayoutsPreference.java new file mode 100644 index 0000000..787800c --- /dev/null +++ b/srcs/juloo.keyboard2/prefs/LayoutsPreference.java @@ -0,0 +1,302 @@ +package juloo.keyboard2.prefs; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.ArrayAdapter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import juloo.keyboard2.*; +import org.json.JSONException; +import org.json.JSONObject; + +public class LayoutsPreference extends ListGroupPreference<LayoutsPreference.Layout> +{ + static final String KEY = "layouts"; + static final List<Layout> DEFAULT = + Collections.singletonList((Layout)new SystemLayout()); + static final ListGroupPreference.Serializer<Layout> SERIALIZER = + new Serializer(); + + /** Text displayed for each layout in the dialog list. */ + String[] _layout_display_names; + + public LayoutsPreference(Context ctx, AttributeSet attrs) + { + super(ctx, attrs); + setKey(KEY); + Resources res = ctx.getResources(); + _layout_display_names = res.getStringArray(R.array.pref_layout_entries); + } + + /** Obtained from [res/values/layouts.xml]. */ + static List<String> _unsafe_layout_ids_str = null; + static TypedArray _unsafe_layout_ids_res = null; + + /** Layout internal names. Contains "system" and "custom". */ + public static List<String> get_layout_names(Resources res) + { + if (_unsafe_layout_ids_str == null) + _unsafe_layout_ids_str = Arrays.asList( + res.getStringArray(R.array.pref_layout_values)); + return _unsafe_layout_ids_str; + } + + /** Layout resource id for a layout name. [-1] if not found. */ + public static int layout_id_of_name(Resources res, String name) + { + if (_unsafe_layout_ids_res == null) + _unsafe_layout_ids_res = res.obtainTypedArray(R.array.layout_ids); + int i = get_layout_names(res).indexOf(name); + if (i >= 0) + return _unsafe_layout_ids_res.getResourceId(i, 0); + return -1; + } + + /** [null] for the "system" layout. */ + public static List<KeyboardData> load_from_preferences(Resources res, SharedPreferences prefs) + { + List<KeyboardData> layouts = new ArrayList<KeyboardData>(); + for (Layout l : load_from_preferences(KEY, prefs, DEFAULT, SERIALIZER)) + { + if (l instanceof NamedLayout) + layouts.add(layout_of_string(res, ((NamedLayout)l).name)); + else if (l instanceof CustomLayout) + layouts.add(((CustomLayout)l).parsed); + else // instanceof SystemLayout + layouts.add(null); + } + return layouts; + } + + /** Does not call [prefs.commit()]. */ + public static void save_to_preferences(SharedPreferences.Editor prefs, List<Layout> items) + { + save_to_preferences(KEY, prefs, items, SERIALIZER); + } + + public static KeyboardData layout_of_string(Resources res, String name) + { + int id = layout_id_of_name(res, name); + if (id > 0) + return KeyboardData.load(res, id); + // Might happen when the app is downgraded, return the system layout. + return null; + } + + @Override + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) + { + super.onSetInitialValue(restoreValue, defaultValue); + if (_values.size() == 0) + set_values(new ArrayList<Layout>(DEFAULT), false); + } + + String label_of_layout(Layout l) + { + if (l instanceof NamedLayout) + { + String lname = ((NamedLayout)l).name; + int value_i = get_layout_names(getContext().getResources()).indexOf(lname); + return value_i < 0 ? lname : _layout_display_names[value_i]; + } + else if (l instanceof CustomLayout) + { + // Use the layout's name if possible + CustomLayout cl = (CustomLayout)l; + if (cl.parsed != null && cl.parsed.name != null + && !cl.parsed.name.equals("")) + return cl.parsed.name; + else + return getContext().getString(R.string.pref_layout_e_custom); + } + else // instanceof SystemLayout + return getContext().getString(R.string.pref_layout_e_system); + } + + @Override + String label_of_value(Layout value, int i) + { + return getContext().getString(R.string.pref_layouts_item, i + 1, + label_of_layout(value)); + } + + @Override + AddButton on_attach_add_button(AddButton prev_btn) + { + if (prev_btn == null) + return new LayoutsAddButton(getContext()); + return prev_btn; + } + + @Override + boolean should_allow_remove_item(Layout value) + { + return (_values.size() > 1 && !(value instanceof CustomLayout)); + } + + @Override + ListGroupPreference.Serializer<Layout> get_serializer() { return SERIALIZER; } + + @Override + void select(final SelectionCallback callback) + { + ArrayAdapter layouts = new ArrayAdapter(getContext(), android.R.layout.simple_list_item_1, _layout_display_names); + new AlertDialog.Builder(getContext()) + .setView(R.layout.dialog_edit_text) + .setAdapter(layouts, new DialogInterface.OnClickListener(){ + public void onClick(DialogInterface _dialog, int which) + { + String name = get_layout_names(getContext().getResources()).get(which); + switch (name) + { + case "system": + callback.select(new SystemLayout()); + break; + case "custom": + select_custom(callback, read_initial_custom_layout()); + break; + default: + callback.select(new NamedLayout(name)); + break; + } + } + }) + .show(); + } + + /** Dialog for specifying a custom layout. [initial_text] is the layout + description when modifying a layout. */ + void select_custom(final SelectionCallback callback, String initial_text) + { + boolean allow_remove = callback.allow_remove() && _values.size() > 1; + CustomLayoutEditDialog.show(getContext(), initial_text, allow_remove, + new CustomLayoutEditDialog.Callback() + { + public void select(String text) + { + if (text == null) + callback.select(null); + else + callback.select(CustomLayout.parse(text)); + } + + public String validate(String text) + { + try + { + KeyboardData.load_string_exn(text); + return null; // Validation passed + } + catch (Exception e) + { + return e.getMessage(); + } + } + }); + } + + /** Called when modifying a layout. Custom layouts behave differently. */ + @Override + void select(final SelectionCallback callback, Layout prev_layout) + { + if (prev_layout instanceof CustomLayout) + select_custom(callback, ((CustomLayout)prev_layout).xml); + else + select(callback); + } + + /** The initial text for the custom layout entry box. The qwerty_us layout is + a good default and contains a bit of documentation. */ + String read_initial_custom_layout() + { + try + { + Resources res = getContext().getResources(); + return Utils.read_all_utf8(res.openRawResource(R.raw.latn_qwerty_us)); + } + catch (Exception _e) + { + return ""; + } + } + + class LayoutsAddButton extends AddButton + { + public LayoutsAddButton(Context ctx) + { + super(ctx); + setLayoutResource(R.layout.pref_layouts_add_btn); + } + } + + /** A layout selected by the user. The only implementations are + [NamedLayout], [SystemLayout] and [CustomLayout]. */ + public interface Layout {} + + public static final class SystemLayout implements Layout + { + public SystemLayout() {} + } + + /** The name of a layout defined in [res/xml]. */ + public static final class NamedLayout implements Layout + { + public final String name; + public NamedLayout(String n) { name = n; } + } + + /** The XML description of a custom layout. */ + public static final class CustomLayout implements Layout + { + public final String xml; + /** Might be null. */ + public final KeyboardData parsed; + public CustomLayout(String xml_, KeyboardData k) { xml = xml_; parsed = k; } + public static CustomLayout parse(String xml) + { + KeyboardData parsed = null; + try { parsed = KeyboardData.load_string_exn(xml); } + catch (Exception e) {} + return new CustomLayout(xml, parsed); + } + } + + /** Named layouts are serialized to strings and custom layouts to JSON + objects with a [kind] field. */ + public static class Serializer implements ListGroupPreference.Serializer<Layout> + { + public Layout load_item(Object obj) throws JSONException + { + if (obj instanceof String) + { + String name = (String)obj; + if (name.equals("system")) + return new SystemLayout(); + return new NamedLayout(name); + } + JSONObject obj_ = (JSONObject)obj; + switch (obj_.getString("kind")) + { + case "custom": return CustomLayout.parse(obj_.getString("xml")); + case "system": default: return new SystemLayout(); + } + } + + public Object save_item(Layout v) throws JSONException + { + if (v instanceof NamedLayout) + return ((NamedLayout)v).name; + if (v instanceof CustomLayout) + return new JSONObject().put("kind", "custom") + .put("xml", ((CustomLayout)v).xml); + return new JSONObject().put("kind", "system"); + } + } +} diff --git a/srcs/juloo.keyboard2/prefs/ListGroupPreference.java b/srcs/juloo.keyboard2/prefs/ListGroupPreference.java new file mode 100644 index 0000000..e332764 --- /dev/null +++ b/srcs/juloo.keyboard2/prefs/ListGroupPreference.java @@ -0,0 +1,294 @@ +package juloo.keyboard2.prefs; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.Preference; +import android.preference.PreferenceGroup; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import java.util.ArrayList; +import java.util.List; +import juloo.keyboard2.*; +import org.json.JSONArray; +import org.json.JSONException; + +/** A list of preferences where the users can add items to the end and modify + and remove items. Backed by a string list. Implement user selection in + [select()]. */ +public abstract class ListGroupPreference<E> extends PreferenceGroup +{ + boolean _attached = false; + List<E> _values; + /** The "add" button currently displayed. */ + AddButton _add_button = null; + + public ListGroupPreference(Context context, AttributeSet attrs) + { + super(context, attrs); + setOrderingAsAdded(true); + setLayoutResource(R.layout.pref_listgroup_group); + _values = new ArrayList<E>(); + } + + /** Overrideable */ + + /** The label to display on the item for a given value. */ + abstract String label_of_value(E value, int i); + + /** Called every time the list changes and allows to change the "Add" button + appearance. + [prev_btn] is the previously attached button, might be null. */ + AddButton on_attach_add_button(AddButton prev_btn) + { + if (prev_btn == null) + return new AddButton(getContext()); + return prev_btn; + } + + /** Called every time the list changes and allows to disable the "Remove" + buttons on every items. Might be used to enforce a minimum number of + items. */ + boolean should_allow_remove_item(E _value) + { + return true; + } + + /** Called when an item is added or modified. */ + abstract void select(SelectionCallback<E> callback); + + /** Called when an item is modified. */ + void select(SelectionCallback<E> callback, E _old_value) + { + select(callback); + } + + /** A separate class is used as the same serializer must be used in the + static context. See [Serializer] below. */ + abstract Serializer<E> get_serializer(); + + /** Load/save utils */ + + /** Read a value saved by preference from a [SharedPreferences] object. + [serializer] must be the same that is returned by [get_serializer()]. + Returns [null] on error. */ + static <E> List<E> load_from_preferences(String key, + SharedPreferences prefs, List<E> def, Serializer<E> serializer) + { + String s = prefs.getString(key, null); + return (s != null) ? load_from_string(s, serializer) : def; + } + + /** Save items into the preferences. Does not call [prefs.commit()]. */ + static <E> void save_to_preferences(String key, SharedPreferences.Editor prefs, List<E> items, Serializer<E> serializer) + { + prefs.putString(key, save_to_string(items, serializer)); + } + + /** Decode a list of string previously encoded with [save_to_string]. Returns + [null] on error. */ + static <E> List<E> load_from_string(String inp, Serializer<E> serializer) + { + try + { + List<E> l = new ArrayList<E>(); + JSONArray arr = new JSONArray(inp); + for (int i = 0; i < arr.length(); i++) + l.add(serializer.load_item(arr.get(i))); + return l; + } + catch (JSONException e) + { + Logs.exn("load_from_string", e); + return null; + } + } + + /** Encode a list of string so it can be passed to + [Preference.persistString()]. Decode with [load_from_string]. */ + static <E> String save_to_string(List<E> items, Serializer<E> serializer) + { + List<Object> serialized_items = new ArrayList<Object>(); + for (E it : items) + { + try + { + serialized_items.add(serializer.save_item(it)); + } + catch (JSONException e) + { + Logs.exn("save_to_string", e); + } + } + return (new JSONArray(serialized_items)).toString(); + } + + /** Protected API */ + + /** Set the values. If [persist] is [true], persist into the store. */ + void set_values(List<E> vs, boolean persist) + { + _values = vs; + reattach(); + if (persist) + persistString(save_to_string(vs, get_serializer())); + } + + void add_item(E v) + { + _values.add(v); + set_values(_values, true); + } + + void change_item(int i, E v) + { + _values.set(i, v); + set_values(_values, true); + } + + void remove_item(int i) + { + _values.remove(i); + set_values(_values, true); + } + + /** Internal */ + + @Override + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) + { + String input = (restoreValue) ? getPersistedString(null) : (String)defaultValue; + if (input != null) + { + List<E> values = load_from_string(input, get_serializer()); + if (values != null) + set_values(values, false); + } + } + + @Override + protected void onAttachedToActivity() + { + super.onAttachedToActivity(); + if (_attached) + return; + _attached = true; + reattach(); + } + + void reattach() + { + if (!_attached) + return; + removeAll(); + int i = 0; + for (E v : _values) + { + addPreference(this.new Item(getContext(), i, v)); + i++; + } + _add_button = on_attach_add_button(_add_button); + _add_button.setOrder(Preference.DEFAULT_ORDER); + addPreference(_add_button); + } + + class Item extends Preference + { + final E _value; + final int _index; + + public Item(Context ctx, int index, E value) + { + super(ctx); + _value = value; + _index = index; + setPersistent(false); + setTitle(label_of_value(value, index)); + if (should_allow_remove_item(value)) + setWidgetLayoutResource(R.layout.pref_listgroup_item_widget); + } + + @Override + protected View onCreateView(ViewGroup parent) + { + View v = super.onCreateView(parent); + View remove_btn = v.findViewById(R.id.pref_listgroup_remove_btn); + if (remove_btn != null) + remove_btn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View _v) + { + remove_item(_index); + } + }); + v.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View _v) + { + select(new SelectionCallback<E>() { + public void select(E value) + { + if (value == null) + remove_item(_index); + else + change_item(_index, value); + } + + public boolean allow_remove() { return true; } + }, _value); + } + }); + return v; + } + } + + class AddButton extends Preference + { + public AddButton(Context ctx) + { + super(ctx); + setPersistent(false); + setLayoutResource(R.layout.pref_listgroup_add_btn); + } + + @Override + protected void onClick() + { + select(new SelectionCallback<E>() { + public void select(E value) + { + add_item(value); + } + + public boolean allow_remove() { return false; } + }); + } + } + + public interface SelectionCallback<E> + { + public void select(E value); + + /** If this method returns [true], [null] might be passed to [select] to + remove the item. */ + public boolean allow_remove(); + } + + /** Methods for serializing and deserializing abstract items. + [StringSerializer] is an implementation. */ + public interface Serializer<E> + { + /** [obj] is an object returned by [save_item()]. */ + E load_item(Object obj) throws JSONException; + + /** Serialize an item into JSON. Might return an object that can be inserted + in a [JSONArray]. */ + Object save_item(E v) throws JSONException; + } + + public static class StringSerializer implements Serializer<String> + { + public String load_item(Object obj) { return (String)obj; } + public Object save_item(String v) { return v; } + } +} diff --git a/srcs/juloo.keyboard2/prefs/SlideBarPreference.java b/srcs/juloo.keyboard2/prefs/SlideBarPreference.java new file mode 100644 index 0000000..cacf37e --- /dev/null +++ b/srcs/juloo.keyboard2/prefs/SlideBarPreference.java @@ -0,0 +1,131 @@ +package juloo.keyboard2.prefs; + +import android.content.Context; +import android.content.res.TypedArray; +import android.preference.DialogPreference; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.SeekBar; + +/* + ** SideBarPreference + ** - + ** Open a dialog showing a seekbar + ** - + ** xml attrs: + ** android:defaultValue Default value (float) + ** min min value (float) + ** max max value (float) + ** - + ** Summary field allow to show the current value using %f or %s flag + */ +public class SlideBarPreference extends DialogPreference + implements SeekBar.OnSeekBarChangeListener +{ + private static final int STEPS = 100; + + private LinearLayout _layout; + private TextView _textView; + private SeekBar _seekBar; + + private float _min; + private float _max; + private float _value; + + private String _initialSummary; + + public SlideBarPreference(Context context, AttributeSet attrs) + { + super(context, attrs); + _initialSummary = getSummary().toString(); + _textView = new TextView(context); + _textView.setPadding(48, 40, 48, 40); + _seekBar = new SeekBar(context); + _seekBar.setOnSeekBarChangeListener(this); + _seekBar.setMax(STEPS); + _min = float_of_string(attrs.getAttributeValue(null, "min")); + _value = _min; + _max = Math.max(1f, float_of_string(attrs.getAttributeValue(null, "max"))); + _layout = new LinearLayout(getContext()); + _layout.setOrientation(LinearLayout.VERTICAL); + _layout.addView(_textView); + _layout.addView(_seekBar); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) + { + _value = Math.round(progress * (_max - _min)) / (float)STEPS + _min; + updateText(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) + { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) + { + } + + @Override + protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) + { + if (restorePersistedValue) + { + _value = getPersistedFloat(_min); + } + else + { + _value = (Float)defaultValue; + persistFloat(_value); + } + _seekBar.setProgress((int)((_value - _min) * STEPS / (_max - _min))); + updateText(); + } + + @Override + protected Object onGetDefaultValue(TypedArray a, int index) + { + return (a.getFloat(index, _min)); + } + + @Override + protected void onDialogClosed(boolean positiveResult) + { + if (positiveResult) + persistFloat(_value); + else + _seekBar.setProgress((int)((getPersistedFloat(_min) - _min) * STEPS / (_max - _min))); + + updateText(); + } + + protected View onCreateDialogView() + { + ViewGroup parent = (ViewGroup)_layout.getParent(); + + if (parent != null) + parent.removeView(_layout); + return (_layout); + } + + private void updateText() + { + String f = String.format(_initialSummary, _value); + + _textView.setText(f); + setSummary(f); + } + + private static float float_of_string(String str) + { + if (str == null) + return (0f); + return (Float.parseFloat(str)); + } +} |
