|
1 /* $Id$ */ |
|
2 |
|
3 /** @file osk_gui.cpp The On Screen Keyboard GUI */ |
|
4 |
|
5 #include "stdafx.h" |
|
6 #include "openttd.h" |
|
7 |
|
8 #include "textbuf_gui.h" |
|
9 #include "window_gui.h" |
|
10 #include "string_func.h" |
|
11 #include "strings_func.h" |
|
12 #include "debug.h" |
|
13 #include "window_func.h" |
|
14 #include "gfx_func.h" |
|
15 |
|
16 #include "table/sprites.h" |
|
17 #include "table/strings.h" |
|
18 |
|
19 struct osk_d { |
|
20 querystr_d *qs; // text-input |
|
21 int text_btn; // widget number of parent's text field |
|
22 int ok_btn; // widget number of parent's ok button (=0 when ok shouldn't be passed on) |
|
23 int cancel_btn; // widget number of parent's cancel button (=0 when cancel shouldn't be passed on; text will be reverted to original) |
|
24 Textbuf *text; // pointer to parent's textbuffer (to update caret position) |
|
25 char *orig; // the original text, in case we cancel |
|
26 }; |
|
27 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(osk_d)); |
|
28 |
|
29 enum OskWidgets { |
|
30 OSK_WIDGET_TEXT = 3, |
|
31 OSK_WIDGET_CANCEL = 5, |
|
32 OSK_WIDGET_OK, |
|
33 OSK_WIDGET_BACKSPACE, |
|
34 OSK_WIDGET_SPECIAL, |
|
35 OSK_WIDGET_CAPS, |
|
36 OSK_WIDGET_SHIFT, |
|
37 OSK_WIDGET_SPACE, |
|
38 OSK_WIDGET_LEFT, |
|
39 OSK_WIDGET_RIGHT, |
|
40 OSK_WIDGET_LETTERS |
|
41 }; |
|
42 |
|
43 char _keyboard_opt[2][OSK_KEYBOARD_ENTRIES * 4 + 1]; |
|
44 static WChar _keyboard[2][OSK_KEYBOARD_ENTRIES]; |
|
45 |
|
46 enum { |
|
47 KEYS_NONE, |
|
48 KEYS_SHIFT, |
|
49 KEYS_CAPS |
|
50 }; |
|
51 static byte _keystate = KEYS_NONE; |
|
52 |
|
53 /* |
|
54 * Only show valid characters; do not show characters that would |
|
55 * only insert a space when we have a spacebar to do that or |
|
56 * characters that are not allowed to be entered. |
|
57 */ |
|
58 static void ChangeOskDiabledState(Window *w, const querystr_d *qs, bool shift) |
|
59 { |
|
60 for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) { |
|
61 w->SetWidgetDisabledState(OSK_WIDGET_LETTERS + i, |
|
62 !IsValidChar(_keyboard[shift][i], qs->afilter) || _keyboard[shift][i] == ' '); |
|
63 } |
|
64 w->SetWidgetDisabledState(OSK_WIDGET_SPACE, !IsValidChar(' ', qs->afilter)); |
|
65 } |
|
66 |
|
67 /* on screen keyboard */ |
|
68 static void OskWndProc(Window *w, WindowEvent *e) |
|
69 { |
|
70 querystr_d *qs = WP(w, osk_d).qs; |
|
71 |
|
72 switch (e->event) { |
|
73 case WE_CREATE: |
|
74 SetBit(_no_scroll, SCROLL_EDIT); |
|
75 /* Not needed by default. */ |
|
76 w->DisableWidget(OSK_WIDGET_SPECIAL); |
|
77 break; |
|
78 |
|
79 case WE_PAINT: { |
|
80 bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT); |
|
81 |
|
82 w->LowerWidget(OSK_WIDGET_TEXT); |
|
83 w->SetWidgetLoweredState(OSK_WIDGET_SHIFT, HasBit(_keystate, KEYS_SHIFT)); |
|
84 w->SetWidgetLoweredState(OSK_WIDGET_CAPS, HasBit(_keystate, KEYS_CAPS)); |
|
85 |
|
86 ChangeOskDiabledState(w, qs, shift); |
|
87 |
|
88 SetDParam(0, qs->caption); |
|
89 DrawWindowWidgets(w); |
|
90 |
|
91 for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) { |
|
92 DrawCharCentered(_keyboard[shift][i], |
|
93 w->widget[OSK_WIDGET_LETTERS + i].left + 8, |
|
94 w->widget[OSK_WIDGET_LETTERS + i].top + 3, |
|
95 TC_BLACK); |
|
96 } |
|
97 |
|
98 DrawEditBox(w, qs, OSK_WIDGET_TEXT); |
|
99 break; |
|
100 } |
|
101 |
|
102 case WE_CLICK: |
|
103 /* clicked a letter */ |
|
104 if (e->we.click.widget >= OSK_WIDGET_LETTERS) { |
|
105 bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT); |
|
106 |
|
107 WChar c = _keyboard[shift][e->we.click.widget - OSK_WIDGET_LETTERS]; |
|
108 |
|
109 if (!IsValidChar(c, qs->afilter)) break; |
|
110 |
|
111 if (InsertTextBufferChar(&qs->text, c)) w->InvalidateWidget(OSK_WIDGET_TEXT); |
|
112 |
|
113 if (HasBit(_keystate, KEYS_SHIFT)) { |
|
114 ToggleBit(_keystate, KEYS_SHIFT); |
|
115 w->widget[OSK_WIDGET_SHIFT].color = HasBit(_keystate, KEYS_SHIFT) ? 15 : 14; |
|
116 SetWindowDirty(w); |
|
117 } |
|
118 break; |
|
119 } |
|
120 |
|
121 switch (e->we.click.widget) { |
|
122 case OSK_WIDGET_BACKSPACE: |
|
123 if (DeleteTextBufferChar(&qs->text, WKC_BACKSPACE)) w->InvalidateWidget(OSK_WIDGET_TEXT); |
|
124 break; |
|
125 |
|
126 case OSK_WIDGET_SPECIAL: |
|
127 /* |
|
128 * Anything device specific can go here. |
|
129 * The button itself is hidden by default, and when you need it you |
|
130 * can not hide it in the create event. |
|
131 */ |
|
132 break; |
|
133 |
|
134 case OSK_WIDGET_CAPS: |
|
135 ToggleBit(_keystate, KEYS_CAPS); |
|
136 SetWindowDirty(w); |
|
137 break; |
|
138 |
|
139 case OSK_WIDGET_SHIFT: |
|
140 ToggleBit(_keystate, KEYS_SHIFT); |
|
141 SetWindowDirty(w); |
|
142 break; |
|
143 |
|
144 case OSK_WIDGET_SPACE: |
|
145 if (InsertTextBufferChar(&qs->text, ' ')) w->InvalidateWidget(OSK_WIDGET_TEXT); |
|
146 break; |
|
147 |
|
148 case OSK_WIDGET_LEFT: |
|
149 if (MoveTextBufferPos(&qs->text, WKC_LEFT)) w->InvalidateWidget(OSK_WIDGET_TEXT); |
|
150 break; |
|
151 |
|
152 case OSK_WIDGET_RIGHT: |
|
153 if (MoveTextBufferPos(&qs->text, WKC_RIGHT)) w->InvalidateWidget(OSK_WIDGET_TEXT); |
|
154 break; |
|
155 |
|
156 case OSK_WIDGET_OK: |
|
157 if (qs->orig == NULL || strcmp(qs->text.buf, qs->orig) != 0) { |
|
158 /* pass information by simulating a button press on parent window */ |
|
159 if (WP(w, osk_d).ok_btn != 0) { |
|
160 Window *parent = w->parent; |
|
161 WindowEvent e; |
|
162 e.event = WE_CLICK; |
|
163 e.we.click.widget = WP(w, osk_d).ok_btn; |
|
164 parent->wndproc(parent, &e); |
|
165 } |
|
166 } |
|
167 DeleteWindow(w); |
|
168 break; |
|
169 |
|
170 case OSK_WIDGET_CANCEL: |
|
171 if (WP(w, osk_d).cancel_btn != 0) { // pass a cancel event to the parent window |
|
172 Window *parent = w->parent; |
|
173 WindowEvent e; |
|
174 e.event = WE_CLICK; |
|
175 e.we.click.widget = WP(w, osk_d).cancel_btn; |
|
176 parent->wndproc(parent, &e); |
|
177 } else { // or reset to original string |
|
178 strcpy(qs->text.buf, WP(w, osk_d).orig); |
|
179 UpdateTextBufferSize(&qs->text); |
|
180 MoveTextBufferPos(&qs->text, WKC_END); |
|
181 } |
|
182 DeleteWindow(w); |
|
183 break; |
|
184 } |
|
185 /* make sure that the parent window's textbox also gets updated */ |
|
186 if (w->parent != NULL) w->parent->InvalidateWidget(WP(w, osk_d).text_btn); |
|
187 break; |
|
188 |
|
189 case WE_MOUSELOOP: |
|
190 HandleEditBox(w, qs, OSK_WIDGET_TEXT); |
|
191 /* make the caret of the parent window also blink */ |
|
192 w->parent->InvalidateWidget(WP(w, osk_d).text_btn); |
|
193 break; |
|
194 } |
|
195 } |
|
196 |
|
197 static const Widget _osk_widgets[] = { |
|
198 { WWT_EMPTY, RESIZE_NONE, 0, 0, 0, 0, 0, 0x0, STR_NULL}, |
|
199 { WWT_CAPTION, RESIZE_NONE, 14, 0, 255, 0, 13, STR_012D, STR_NULL}, |
|
200 { WWT_PANEL, RESIZE_NONE, 14, 0, 255, 14, 29, 0x0, STR_NULL}, |
|
201 { WWT_EDITBOX, RESIZE_NONE, 14, 2, 253, 16, 27, 0x0, STR_NULL}, |
|
202 |
|
203 { WWT_PANEL, RESIZE_NONE, 14, 0, 255, 30, 139, 0x0, STR_NULL}, |
|
204 |
|
205 { WWT_TEXTBTN, RESIZE_NONE, 14, 3, 108, 35, 46, STR_012E_CANCEL, STR_NULL}, |
|
206 { WWT_TEXTBTN, RESIZE_NONE, 14, 111, 216, 35, 46, STR_012F_OK, STR_NULL}, |
|
207 { WWT_PUSHIMGBTN, RESIZE_NONE, 14, 219, 252, 35, 46, SPR_OSK_BACKSPACE, STR_NULL}, |
|
208 |
|
209 { WWT_PUSHIMGBTN, RESIZE_NONE, 14, 3, 27, 67, 82, SPR_OSK_SPECIAL, STR_NULL}, |
|
210 { WWT_IMGBTN, RESIZE_NONE, 14, 3, 36, 85, 100, SPR_OSK_CAPS, STR_NULL}, |
|
211 { WWT_IMGBTN, RESIZE_NONE, 14, 3, 27, 103, 118, SPR_OSK_SHIFT, STR_NULL}, |
|
212 |
|
213 { WWT_PUSHTXTBTN, RESIZE_NONE, 14, 75, 189, 121, 136, STR_EMPTY, STR_NULL}, |
|
214 |
|
215 { WWT_PUSHIMGBTN, RESIZE_NONE, 14, 219, 234, 121, 136, SPR_OSK_LEFT, STR_NULL}, |
|
216 { WWT_PUSHIMGBTN, RESIZE_NONE, 14, 237, 252, 121, 136, SPR_OSK_RIGHT, STR_NULL}, |
|
217 |
|
218 { WWT_PUSHBTN, RESIZE_NONE, 14, 3, 18, 49, 64, 0x0, STR_NULL}, |
|
219 { WWT_PUSHBTN, RESIZE_NONE, 14, 21, 36, 49, 64, 0x0, STR_NULL}, |
|
220 { WWT_PUSHBTN, RESIZE_NONE, 14, 39, 54, 49, 64, 0x0, STR_NULL}, |
|
221 { WWT_PUSHBTN, RESIZE_NONE, 14, 57, 72, 49, 64, 0x0, STR_NULL}, |
|
222 { WWT_PUSHBTN, RESIZE_NONE, 14, 75, 90, 49, 64, 0x0, STR_NULL}, |
|
223 { WWT_PUSHBTN, RESIZE_NONE, 14, 93, 108, 49, 64, 0x0, STR_NULL}, |
|
224 { WWT_PUSHBTN, RESIZE_NONE, 14, 111, 126, 49, 64, 0x0, STR_NULL}, |
|
225 { WWT_PUSHBTN, RESIZE_NONE, 14, 129, 144, 49, 64, 0x0, STR_NULL}, |
|
226 { WWT_PUSHBTN, RESIZE_NONE, 14, 147, 162, 49, 64, 0x0, STR_NULL}, |
|
227 { WWT_PUSHBTN, RESIZE_NONE, 14, 165, 180, 49, 64, 0x0, STR_NULL}, |
|
228 { WWT_PUSHBTN, RESIZE_NONE, 14, 183, 198, 49, 64, 0x0, STR_NULL}, |
|
229 { WWT_PUSHBTN, RESIZE_NONE, 14, 201, 216, 49, 64, 0x0, STR_NULL}, |
|
230 { WWT_PUSHBTN, RESIZE_NONE, 14, 219, 234, 49, 64, 0x0, STR_NULL}, |
|
231 { WWT_PUSHBTN, RESIZE_NONE, 14, 237, 252, 49, 64, 0x0, STR_NULL}, |
|
232 |
|
233 { WWT_PUSHBTN, RESIZE_NONE, 14, 30, 45, 67, 82, 0x0, STR_NULL}, |
|
234 { WWT_PUSHBTN, RESIZE_NONE, 14, 48, 63, 67, 82, 0x0, STR_NULL}, |
|
235 { WWT_PUSHBTN, RESIZE_NONE, 14, 66, 81, 67, 82, 0x0, STR_NULL}, |
|
236 { WWT_PUSHBTN, RESIZE_NONE, 14, 84, 99, 67, 82, 0x0, STR_NULL}, |
|
237 { WWT_PUSHBTN, RESIZE_NONE, 14, 102, 117, 67, 82, 0x0, STR_NULL}, |
|
238 { WWT_PUSHBTN, RESIZE_NONE, 14, 120, 135, 67, 82, 0x0, STR_NULL}, |
|
239 { WWT_PUSHBTN, RESIZE_NONE, 14, 138, 153, 67, 82, 0x0, STR_NULL}, |
|
240 { WWT_PUSHBTN, RESIZE_NONE, 14, 156, 171, 67, 82, 0x0, STR_NULL}, |
|
241 { WWT_PUSHBTN, RESIZE_NONE, 14, 174, 189, 67, 82, 0x0, STR_NULL}, |
|
242 { WWT_PUSHBTN, RESIZE_NONE, 14, 192, 207, 67, 82, 0x0, STR_NULL}, |
|
243 { WWT_PUSHBTN, RESIZE_NONE, 14, 210, 225, 67, 82, 0x0, STR_NULL}, |
|
244 { WWT_PUSHBTN, RESIZE_NONE, 14, 228, 243, 67, 82, 0x0, STR_NULL}, |
|
245 |
|
246 { WWT_PUSHBTN, RESIZE_NONE, 14, 39, 54, 85, 100, 0x0, STR_NULL}, |
|
247 { WWT_PUSHBTN, RESIZE_NONE, 14, 57, 72, 85, 100, 0x0, STR_NULL}, |
|
248 { WWT_PUSHBTN, RESIZE_NONE, 14, 75, 90, 85, 100, 0x0, STR_NULL}, |
|
249 { WWT_PUSHBTN, RESIZE_NONE, 14, 93, 108, 85, 100, 0x0, STR_NULL}, |
|
250 { WWT_PUSHBTN, RESIZE_NONE, 14, 111, 126, 85, 100, 0x0, STR_NULL}, |
|
251 { WWT_PUSHBTN, RESIZE_NONE, 14, 129, 144, 85, 100, 0x0, STR_NULL}, |
|
252 { WWT_PUSHBTN, RESIZE_NONE, 14, 147, 162, 85, 100, 0x0, STR_NULL}, |
|
253 { WWT_PUSHBTN, RESIZE_NONE, 14, 165, 180, 85, 100, 0x0, STR_NULL}, |
|
254 { WWT_PUSHBTN, RESIZE_NONE, 14, 183, 198, 85, 100, 0x0, STR_NULL}, |
|
255 { WWT_PUSHBTN, RESIZE_NONE, 14, 201, 216, 85, 100, 0x0, STR_NULL}, |
|
256 { WWT_PUSHBTN, RESIZE_NONE, 14, 219, 234, 85, 100, 0x0, STR_NULL}, |
|
257 { WWT_PUSHBTN, RESIZE_NONE, 14, 237, 252, 85, 100, 0x0, STR_NULL}, |
|
258 |
|
259 { WWT_PUSHBTN, RESIZE_NONE, 14, 30, 45, 103, 118, 0x0, STR_NULL}, |
|
260 { WWT_PUSHBTN, RESIZE_NONE, 14, 48, 63, 103, 118, 0x0, STR_NULL}, |
|
261 { WWT_PUSHBTN, RESIZE_NONE, 14, 66, 81, 103, 118, 0x0, STR_NULL}, |
|
262 { WWT_PUSHBTN, RESIZE_NONE, 14, 84, 99, 103, 118, 0x0, STR_NULL}, |
|
263 { WWT_PUSHBTN, RESIZE_NONE, 14, 102, 117, 103, 118, 0x0, STR_NULL}, |
|
264 { WWT_PUSHBTN, RESIZE_NONE, 14, 120, 135, 103, 118, 0x0, STR_NULL}, |
|
265 { WWT_PUSHBTN, RESIZE_NONE, 14, 138, 153, 103, 118, 0x0, STR_NULL}, |
|
266 { WWT_PUSHBTN, RESIZE_NONE, 14, 156, 171, 103, 118, 0x0, STR_NULL}, |
|
267 { WWT_PUSHBTN, RESIZE_NONE, 14, 174, 189, 103, 118, 0x0, STR_NULL}, |
|
268 { WWT_PUSHBTN, RESIZE_NONE, 14, 192, 207, 103, 118, 0x0, STR_NULL}, |
|
269 { WWT_PUSHBTN, RESIZE_NONE, 14, 210, 225, 103, 118, 0x0, STR_NULL}, |
|
270 { WWT_PUSHBTN, RESIZE_NONE, 14, 228, 243, 103, 118, 0x0, STR_NULL}, |
|
271 |
|
272 { WIDGETS_END}, |
|
273 }; |
|
274 |
|
275 WindowDesc _osk_desc = { |
|
276 WDP_CENTER, WDP_CENTER, 256, 140, 256, 140, |
|
277 WC_OSK, WC_NONE, |
|
278 WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, |
|
279 _osk_widgets, |
|
280 OskWndProc |
|
281 }; |
|
282 |
|
283 /** |
|
284 * Retrieve keyboard layout from language string or (if set) config file. |
|
285 * Also check for invalid characters. |
|
286 */ |
|
287 void GetKeyboardLayout() |
|
288 { |
|
289 char keyboard[2][OSK_KEYBOARD_ENTRIES * 4 + 1]; |
|
290 char errormark[2][OSK_KEYBOARD_ENTRIES + 1]; // used for marking invalid chars |
|
291 bool has_error = false; // true when an invalid char is detected |
|
292 |
|
293 if (StrEmpty(_keyboard_opt[0])) { |
|
294 GetString(keyboard[0], STR_OSK_KEYBOARD_LAYOUT, lastof(keyboard[0])); |
|
295 } else { |
|
296 strncpy(keyboard[0], _keyboard_opt[0], lengthof(keyboard[0])); |
|
297 } |
|
298 |
|
299 if (StrEmpty(_keyboard_opt[1])) { |
|
300 GetString(keyboard[1], STR_OSK_KEYBOARD_LAYOUT_CAPS, lastof(keyboard[1])); |
|
301 } else { |
|
302 strncpy(keyboard[0], _keyboard_opt[0], lengthof(keyboard[1])); |
|
303 } |
|
304 |
|
305 for (uint j = 0; j < 2; j++) { |
|
306 const char *kbd = keyboard[j]; |
|
307 for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) { |
|
308 _keyboard[j][i] = Utf8Consume(&kbd); |
|
309 |
|
310 if (IsPrintable(_keyboard[j][i])) { |
|
311 errormark[j][i] = ' '; |
|
312 } else { |
|
313 has_error = true; |
|
314 errormark[j][i] = '^'; |
|
315 _keyboard[j][i] = ' '; |
|
316 } |
|
317 } |
|
318 } |
|
319 |
|
320 if (has_error) { |
|
321 ShowInfoF("The keyboard layout you selected contains invalid chars. Please check those chars marked with ^."); |
|
322 ShowInfoF("Normal keyboard: %s", keyboard[0]); |
|
323 ShowInfoF(" %s", errormark[0]); |
|
324 ShowInfoF("Caps Lock: %s", keyboard[1]); |
|
325 ShowInfoF(" %s", errormark[1]); |
|
326 } |
|
327 } |
|
328 |
|
329 /** |
|
330 * Show the osk associated with a given textbox |
|
331 * @param parent pointer to the Window where this keyboard originated from |
|
332 * @param q querystr_d pointer to the query string of the parent, which is |
|
333 * shared for both windows |
|
334 * @param button widget number of parent's textbox |
|
335 * @param cancel widget number of parent's cancel button (0 if cancel events |
|
336 * should not be passed) |
|
337 * @param ok widget number of parent's ok button (0 if ok events should not |
|
338 * be passed) |
|
339 */ |
|
340 void ShowOnScreenKeyboard(Window *parent, querystr_d *q, int button, int cancel, int ok) |
|
341 { |
|
342 DeleteWindowById(WC_OSK, 0); |
|
343 |
|
344 Window *w = AllocateWindowDesc(&_osk_desc); |
|
345 |
|
346 w->parent = parent; |
|
347 assert(parent != NULL); |
|
348 |
|
349 if (parent->widget[button].data != 0) q->caption = parent->widget[button].data; |
|
350 |
|
351 WP(w, osk_d).qs = q; |
|
352 WP(w, osk_d).text_btn = button; |
|
353 WP(w, osk_d).cancel_btn = cancel; |
|
354 WP(w, osk_d).ok_btn = ok; |
|
355 WP(w, osk_d).text = &q->text; |
|
356 |
|
357 GetKeyboardLayout(); |
|
358 |
|
359 /* make a copy in case we need to reset later */ |
|
360 strcpy(_orig_str_buf, WP(w, osk_d).qs->text.buf); |
|
361 WP(w, osk_d).orig = _orig_str_buf; |
|
362 } |