1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
|
package juloo.keyboard2;
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 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]. */
interface Layout {}
static final class SystemLayout implements Layout
{
public SystemLayout() {}
}
/** The name of a layout defined in [res/xml]. */
static final class NamedLayout implements Layout
{
public final String name;
public NamedLayout(String n) { name = n; }
}
/** The XML description of a custom layout. */
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. */
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");
}
}
}
|