abouttreesummaryrefslogcommitdiff
path: root/srcs/juloo.keyboard2/prefs
diff options
context:
space:
mode:
authorJules Aguillon2024-01-13 20:59:05 +0100
committerJules Aguillon2024-01-13 20:59:05 +0100
commiteddf9c6c117449012e2aece5776694467e3483f0 (patch)
tree0f503419b7beffc15f6f0903d95e68d1c4bdddd1 /srcs/juloo.keyboard2/prefs
parent148f3dfc052bc6a4161073e59505bd547ab347c3 (diff)
downloadunexpected-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.java70
-rw-r--r--srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java177
-rw-r--r--srcs/juloo.keyboard2/prefs/IntSlideBarPreference.java120
-rw-r--r--srcs/juloo.keyboard2/prefs/LayoutsPreference.java302
-rw-r--r--srcs/juloo.keyboard2/prefs/ListGroupPreference.java294
-rw-r--r--srcs/juloo.keyboard2/prefs/SlideBarPreference.java131
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));
+ }
+}