1 /*
2  * KeymapPreference.java - Even simpler preference for mapping keys
3  *
4  * Copyright (C) 2010 Kostas Nakos
5  * Copyright (C) 2010 Atari800 development team (see DOC/CREDITS)
6  *
7  * This file is part of the Atari800 emulator project which emulates
8  * the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers.
9  *
10  * Atari800 is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * Atari800 is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Atari800; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23 */
24 
25 package name.nick.jubanka.colleen;
26 
27 import android.preference.DialogPreference;
28 import android.content.Context;
29 import android.view.KeyCharacterMap;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.KeyEvent;
33 import android.content.res.TypedArray;
34 import android.util.AttributeSet;
35 import android.util.Log;
36 import android.content.DialogInterface;
37 import android.os.Bundle;
38 import android.app.Dialog;
39 import android.app.AlertDialog;
40 import android.util.SparseArray;
41 import android.widget.TextView;
42 import android.widget.EditText;
43 import android.R.style;
44 import android.util.TypedValue;
45 import android.view.LayoutInflater;
46 import static android.view.KeyEvent.*;
47 import static name.nick.jubanka.colleen.A800view.*;
48 
49 
50 public final class KeymapPreference extends DialogPreference
51 {
52 	private static final String TAG = "KeyPreference";
53 
54 	private static final int DEFKEY = 'a';
55 	private static final String DEFKEYEXT = "-1,-1";
56 	private static final int EXTSTR_ACTION = 0;
57 	private static final int EXTSTR_KEY = 1;
58 
59 	private KeyCharacterMap _keymap;
60 	private int _def;
61 	private String _defext = null;
62 	private boolean _extended = false;
63 
KeymapPreference(Context c, AttributeSet a)64 	public KeymapPreference(Context c, AttributeSet a) {
65 		super(c, a);
66 		_keymap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
67 
68 		_extended = Boolean.parseBoolean( c.obtainStyledAttributes(a, R.styleable.KeymapPreference)
69 										   .getString(R.styleable.KeymapPreference_ext) );
70 
71 		setNegativeButtonText(R.string.cancel);
72 		setDialogTitle(getTitle());
73 	}
74 
75 	@Override
onGetDefaultValue(TypedArray a, int i)76 	protected Object onGetDefaultValue(TypedArray a, int i) {
77 		try {
78 			_def = a.getInt(i, DEFKEY);
79 			return _def;
80 		} catch (NumberFormatException e) {
81 			_defext = a.getString(i);
82 			if (_defext == null)
83 				_defext = DEFKEYEXT;
84 			return _defext;
85 		}
86 	}
87 
88 	@Override
onSetInitialValue(boolean restore, Object def)89 	protected void onSetInitialValue(boolean restore, Object def) {
90 		if (!restore)
91 			if (!_extended)
92 				persistInt((Integer) def);
93 			else
94 				persistString((String) def);
95 	}
96 
97 	@Override
showDialog(Bundle state)98 	protected void showDialog(Bundle state) {
99 		super.showDialog(state);
100 		Dialog d = getDialog();
101 		d.takeKeyEvents(true);
102 		((AlertDialog) d).getButton(DialogInterface.BUTTON_POSITIVE).setVisibility(View.GONE);
103 	}
104 
105 	@Override
onCreateDialogView()106 	protected View onCreateDialogView() {
107 		View v = new SnoopTextView(getContext());
108 		return v;
109 	}
110 
111 	private final class SnoopTextView extends EditText
112 	{
SnoopTextView(Context c)113 		public SnoopTextView(Context c) {
114 			super(c);
115 			setText( (!_extended) ? R.string.pref_keymapmsg : R.string.pref_keymapmsg1);
116 			setCursorVisible(false);
117 			int pad = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
118 													  (float) 10,
119 													  c.getResources().getDisplayMetrics());
120 			setPadding(pad, pad, pad, pad);
121 			setFocusable(true);
122 			setFocusableInTouchMode(true);
123 			requestFocus();
124 		}
125 
126 		@Override
onKeyDown(int kc, KeyEvent ev)127 		public boolean onKeyDown(int kc, KeyEvent ev) {
128 			Log.d(TAG, "key " + kc);
129 
130 			for (int res: RESKEYS)
131 				if (res == kc)
132 					return false;
133 
134 			final int k = xlatKey(kc);
135 
136 			if (k == 0)
137 				return false;
138 
139 			if (k == getKeymap() && !_extended) {
140 				getDialog().dismiss();
141 				return true;
142 			}
143 
144 			if (callChangeListener(new Integer(k)) == false) {
145 				new AlertDialog.Builder(getContext())
146 					.setTitle(R.string.warning)
147 					.setIcon(android.R.drawable.ic_dialog_alert)
148 					.setMessage(R.string.pref_keymapdupmsg)
149 					.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
150 						public void onClick(DialogInterface d, int i) {
151 							callChangeListener(new Integer(-k));
152 							setKeymap(k);
153 							d.dismiss();
154 							if (!_extended) {
155 								Dialog d1 = getDialog();
156 								if (d1 != null)
157 									d1.dismiss();
158 							} else
159 								showExtDialog();
160 						}
161 						})
162 					.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
163 						public void onClick(DialogInterface d, int i) {
164 							Dialog d1 = getDialog();
165 							if (d1 != null)
166 								d1.dismiss();
167 						}
168 						})
169 					.show();
170 				return true;
171 			}
172 
173 			setKeymap(k);
174 			if (!_extended)
175 				getDialog().dismiss();
176 			else
177 				showExtDialog();
178 			return true;
179 		}
180 	}
181 
showExtDialog()182 	private void showExtDialog()
183 	{
184 		LayoutInflater inf = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
185 		new AlertDialog.Builder(getContext())
186 			.setTitle(getTitle())
187 			.setView(inf.inflate(R.layout.extended_keymap, null))
188 			.setCancelable(false)
189 			.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
190 				public void onClick(DialogInterface d, int i) {
191 					CharSequence txt = ((TextView) ((Dialog) d).findViewById(R.id.keyinput)).getText();
192 					if (txt == null || txt.length() != 1) {
193 						getDialog().dismiss();
194 						return;
195 					}
196 					persistString( buildExtPref(parseExtPref(EXTSTR_ACTION), (int) txt.charAt(0)) );
197 					updateSum();
198 					d.dismiss();
199 					getDialog().dismiss();
200 				}
201 				})
202 			.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
203 				public void onClick(DialogInterface d, int i) {
204 					getDialog().dismiss();
205 				}
206 				})
207 			.show();
208 	}
209 
updateSum()210 	public void updateSum() {
211 		StringBuilder str = new StringBuilder();
212 		str.append( getContext()
213 					.getString(_extended ? R.string.pref_keymap_controller : R.string.pref_keymap_current) );
214 		str.append(" ");
215 		str.append( getKeyname(getKeymap()) );
216 		if (_extended) {
217 			str.append(" ");
218 			str.append( getContext().getString(R.string.pref_keymap_mappedto) );
219 			str.append(" ");
220 			str.append( getKeyname(parseExtPref(EXTSTR_KEY)) );
221 		}
222 		setSummary(str);
223 	}
224 
xlatKey(int kc)225 	private int xlatKey(int kc) {
226 		int k;
227 
228 		Integer xlat = A800view.XLATKEYS.get(kc);
229 		if (xlat != null)
230 			k = xlat.intValue();
231 		else
232 			k = _keymap.get(kc, 0);
233 		return k;
234 	}
235 
setKeymap(int k)236 	public void setKeymap(int k) {
237 		if (!_extended)
238 			persistInt(k);
239 		else
240 			persistString( buildExtPref(k, parseExtPref(EXTSTR_KEY)) );
241 		updateSum();
242 	}
243 
setDefaultKeymap()244 	public void setDefaultKeymap() {
245 		if (!_extended)		return;
246 		persistString(DEFKEYEXT);
247 		updateSum();
248 	}
249 
getKeymap()250 	public int getKeymap() {
251 		if (!_extended)
252 			return getPersistedInt(-128) == -128 ? _def : getPersistedInt(-1);
253 		else
254 			return parseExtPref(EXTSTR_ACTION);
255 	}
256 
parseExtPref(int part)257 	private int parseExtPref(int part) {
258 		String str = getPersistedString(null);
259 		return Integer.parseInt( ((str != null) ? str : _defext).split(",")[part] );
260 	}
261 
buildExtPref(int k1, int k2)262 	private String buildExtPref(int k1, int k2) {
263 		return Integer.toString(k1) + "," + Integer.toString(k2);
264 	}
265 
getKeyname(int k)266 	private String getKeyname(int k) {
267 		String name = null;
268 		name = KEYNAMES.get(k);
269 		if (name != null)				return name;
270 		if (k > 31 && k < 127)			return Character.toString((char) k);
271 		return "ASCII " + k;
272 	}
273 
274 	// Real programmers *hate* data entry ;-)
275 	private static final SparseArray<String> KEYNAMES = new SparseArray<String>(13);
276 	static {
277 		KEYNAMES.put(-1,			"None");
278 		KEYNAMES.put(' ',			"Space");
KEYNAMES.put(KEY_DOWN, R)279 		KEYNAMES.put(KEY_DOWN,		"Down arrow");
KEYNAMES.put(KEY_LEFT, R)280 		KEYNAMES.put(KEY_LEFT,		"Left arrow");
KEYNAMES.put(KEY_RIGHT, R)281 		KEYNAMES.put(KEY_RIGHT,		"Right arrow");
KEYNAMES.put(KEY_UP, R)282 		KEYNAMES.put(KEY_UP,		"Up arrow");
KEYNAMES.put(KEY_ENTER, R)283 		KEYNAMES.put(KEY_ENTER,		"Enter");
KEYNAMES.put(KEY_BACKSPACE, R)284 		KEYNAMES.put(KEY_BACKSPACE,	"Del");
KEYNAMES.put(KEY_BT_X, R)285 		KEYNAMES.put(KEY_BT_X,		"Button X");
KEYNAMES.put(KEY_BT_Y, R)286 		KEYNAMES.put(KEY_BT_Y,		"Button Y");
KEYNAMES.put(KEY_BT_L1, R)287 		KEYNAMES.put(KEY_BT_L1,		"Button L1");
KEYNAMES.put(KEY_BT_R1, R)288 		KEYNAMES.put(KEY_BT_R1,		"Button R1");
KEYNAMES.put(KEY_BREAK, R)289 		KEYNAMES.put(KEY_BREAK,		"DPAD Enter");
290 	}
291 	private static final int[] RESKEYS = {
292 		KEYCODE_SHIFT_LEFT, KEYCODE_SHIFT_RIGHT, KEYCODE_VOLUME_UP, KEYCODE_VOLUME_DOWN,
293 		KEYCODE_MENU, KEYCODE_SEARCH, KEYCODE_BACK, KEYCODE_HOME, KEYCODE_POWER,
294 		KEYCODE_CALL, KEYCODE_ENDCALL
295 	};
296 }
297