1 /*************************************************************************/
2 /*  GodotEditText.java                                                   */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
10 /*                                                                       */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the       */
13 /* "Software"), to deal in the Software without restriction, including   */
14 /* without limitation the rights to use, copy, modify, merge, publish,   */
15 /* distribute, sublicense, and/or sell copies of the Software, and to    */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions:                                             */
18 /*                                                                       */
19 /* The above copyright notice and this permission notice shall be        */
20 /* included in all copies or substantial portions of the Software.       */
21 /*                                                                       */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
29 /*************************************************************************/
30 
31 package org.godotengine.godot.input;
32 
33 import org.godotengine.godot.*;
34 
35 import android.content.Context;
36 import android.os.Handler;
37 import android.os.Message;
38 import android.text.InputFilter;
39 import android.text.InputType;
40 import android.util.AttributeSet;
41 import android.view.KeyEvent;
42 import android.view.inputmethod.EditorInfo;
43 import android.view.inputmethod.InputMethodManager;
44 import android.widget.EditText;
45 
46 import java.lang.ref.WeakReference;
47 
48 public class GodotEditText extends EditText {
49 	// ===========================================================
50 	// Constants
51 	// ===========================================================
52 	private final static int HANDLER_OPEN_IME_KEYBOARD = 2;
53 	private final static int HANDLER_CLOSE_IME_KEYBOARD = 3;
54 
55 	// ===========================================================
56 	// Fields
57 	// ===========================================================
58 	private GodotView mView;
59 	private GodotTextInputWrapper mInputWrapper;
60 	private EditHandler sHandler = new EditHandler(this);
61 	private String mOriginText;
62 	private int mMaxInputLength = Integer.MAX_VALUE;
63 	private boolean mMultiline = false;
64 
65 	private static class EditHandler extends Handler {
66 		private final WeakReference<GodotEditText> mEdit;
EditHandler(GodotEditText edit)67 		public EditHandler(GodotEditText edit) {
68 			mEdit = new WeakReference<>(edit);
69 		}
70 
71 		@Override
handleMessage(Message msg)72 		public void handleMessage(Message msg) {
73 			GodotEditText edit = mEdit.get();
74 			if (edit != null) {
75 				edit.handleMessage(msg);
76 			}
77 		}
78 	}
79 
80 	// ===========================================================
81 	// Constructors
82 	// ===========================================================
GodotEditText(final Context context)83 	public GodotEditText(final Context context) {
84 		super(context);
85 		this.initView();
86 	}
87 
GodotEditText(final Context context, final AttributeSet attrs)88 	public GodotEditText(final Context context, final AttributeSet attrs) {
89 		super(context, attrs);
90 		this.initView();
91 	}
92 
GodotEditText(final Context context, final AttributeSet attrs, final int defStyle)93 	public GodotEditText(final Context context, final AttributeSet attrs, final int defStyle) {
94 		super(context, attrs, defStyle);
95 		this.initView();
96 	}
97 
initView()98 	protected void initView() {
99 		this.setPadding(0, 0, 0, 0);
100 		this.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_ACTION_DONE);
101 	}
102 
isMultiline()103 	public boolean isMultiline() {
104 		return mMultiline;
105 	}
106 
handleMessage(final Message msg)107 	private void handleMessage(final Message msg) {
108 		switch (msg.what) {
109 			case HANDLER_OPEN_IME_KEYBOARD: {
110 				GodotEditText edit = (GodotEditText)msg.obj;
111 				String text = edit.mOriginText;
112 				if (edit.requestFocus()) {
113 					edit.removeTextChangedListener(edit.mInputWrapper);
114 					setMaxInputLength(edit);
115 					edit.setText("");
116 					edit.append(text);
117 					if (msg.arg2 != -1) {
118 						edit.setSelection(msg.arg1, msg.arg2);
119 						edit.mInputWrapper.setSelection(true);
120 					} else {
121 						edit.mInputWrapper.setSelection(false);
122 					}
123 
124 					int inputType = InputType.TYPE_CLASS_TEXT;
125 					if (edit.isMultiline()) {
126 						inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE;
127 					}
128 					edit.setInputType(inputType);
129 
130 					edit.mInputWrapper.setOriginText(text);
131 					edit.addTextChangedListener(edit.mInputWrapper);
132 					final InputMethodManager imm = (InputMethodManager)mView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
133 					imm.showSoftInput(edit, 0);
134 				}
135 			} break;
136 
137 			case HANDLER_CLOSE_IME_KEYBOARD: {
138 				GodotEditText edit = (GodotEditText)msg.obj;
139 
140 				edit.removeTextChangedListener(mInputWrapper);
141 				final InputMethodManager imm = (InputMethodManager)mView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
142 				imm.hideSoftInputFromWindow(edit.getWindowToken(), 0);
143 				edit.mView.requestFocus();
144 			} break;
145 		}
146 	}
147 
setMaxInputLength(EditText p_edit_text)148 	private void setMaxInputLength(EditText p_edit_text) {
149 		InputFilter[] filters = new InputFilter[1];
150 		filters[0] = new InputFilter.LengthFilter(this.mMaxInputLength);
151 		p_edit_text.setFilters(filters);
152 	}
153 
154 	// ===========================================================
155 	// Getter & Setter
156 	// ===========================================================
setView(final GodotView view)157 	public void setView(final GodotView view) {
158 		this.mView = view;
159 		if (mInputWrapper == null)
160 			mInputWrapper = new GodotTextInputWrapper(mView, this);
161 		this.setOnEditorActionListener(mInputWrapper);
162 		view.requestFocus();
163 	}
164 
165 	// ===========================================================
166 	// Methods for/from SuperClass/Interfaces
167 	// ===========================================================
168 	@Override
onKeyDown(final int keyCode, final KeyEvent keyEvent)169 	public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
170 		super.onKeyDown(keyCode, keyEvent);
171 
172 		/* Let GlSurfaceView get focus if back key is input. */
173 		if (keyCode == KeyEvent.KEYCODE_BACK) {
174 			this.mView.requestFocus();
175 		}
176 
177 		return true;
178 	}
179 
180 	// ===========================================================
181 	// Methods
182 	// ===========================================================
showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end)183 	public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
184 		int maxInputLength = (p_max_input_length <= 0) ? Integer.MAX_VALUE : p_max_input_length;
185 		if (p_cursor_start == -1) { // cursor position not given
186 			this.mOriginText = p_existing_text;
187 			this.mMaxInputLength = maxInputLength;
188 		} else if (p_cursor_end == -1) { // not text selection
189 			this.mOriginText = p_existing_text.substring(0, p_cursor_start);
190 			this.mMaxInputLength = maxInputLength - (p_existing_text.length() - p_cursor_start);
191 		} else {
192 			this.mOriginText = p_existing_text.substring(0, p_cursor_end);
193 			this.mMaxInputLength = maxInputLength - (p_existing_text.length() - p_cursor_end);
194 		}
195 
196 		this.mMultiline = p_multiline;
197 
198 		final Message msg = new Message();
199 		msg.what = HANDLER_OPEN_IME_KEYBOARD;
200 		msg.obj = this;
201 		msg.arg1 = p_cursor_start;
202 		msg.arg2 = p_cursor_end;
203 		sHandler.sendMessage(msg);
204 	}
205 
hideKeyboard()206 	public void hideKeyboard() {
207 		final Message msg = new Message();
208 		msg.what = HANDLER_CLOSE_IME_KEYBOARD;
209 		msg.obj = this;
210 		sHandler.sendMessage(msg);
211 	}
212 
213 	// ===========================================================
214 	// Inner and Anonymous Classes
215 	// ===========================================================
216 }
217