OpenTTD

- Main Page
- Related Pages
- Modules
- Namespaces
- Data Structures
- Files

- File List
- Globals

osk_gui.cpp
Go to the documentation of this file.
1 /* $Id: osk_gui.cpp 26538 2014-04-28 21:06:51Z rubidium $ */
2
3 /*
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
9
12 #include "stdafx.h"
13 #include "string_func.h"
14 #include "strings_func.h"
15 #include "debug.h"
16 #include "window_func.h"
17 #include "gfx_func.h"
18 #include "querystring_gui.h"
19 #include "video/video_driver.hpp"
20
21 #include "widgets/osk_widget.h"
22
23 #include "table/sprites.h"
24 #include "table/strings.h"
25
26 #include "safeguards.h"
27
28 char _keyboard_opt[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
29 static WChar _keyboard[2][OSK_KEYBOARD_ENTRIES];
30
31 enum KeyStateBits {
32 KEYS_NONE,
33 KEYS_SHIFT,
34 KEYS_CAPS
35 };
36 static byte _keystate = KEYS_NONE;
37
38 struct OskWindow : public Window {
39 StringID caption;
40 QueryString *qs;
41 int text_btn;
42 Textbuf *text;
43 char *orig_str_buf;
44 bool shift;
45
46 OskWindow(WindowDesc *desc, Window *parent, int button) : Window(desc)
47 {
48 this->parent = parent;
49 assert(parent != NULL);
50
51 NWidgetCore *par_wid = parent->GetWidget<NWidgetCore>(button);
52 assert(par_wid != NULL);
53
54 assert(parent->querystrings.Contains(button));
55 this->qs = parent->querystrings.Find(button)->second;
56 this->caption = (par_wid->widget_data != STR_NULL) ? par_wid->widget_data : this->qs->caption;
57 this->text_btn = button;
58 this->text = &this->qs->text;
59 this->querystrings[WID_OSK_TEXT] = this->qs;
60
61 /* make a copy in case we need to reset later */
62 this->orig_str_buf = stredup(this->qs->text.buf);
63
64 this->InitNested(0);
65 this->SetFocusedWidget(WID_OSK_TEXT);
66
67 /* Not needed by default. */
68 this->DisableWidget(WID_OSK_SPECIAL);
69
70 this->UpdateOskState();
71 }
72
73 ~OskWindow()
74 {
75 free(this->orig_str_buf);
76 }
77
83 void UpdateOskState()
84 {
85 this->shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
86
87 for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
88 this->SetWidgetDisabledState(WID_OSK_LETTERS + i,
89 !IsValidChar(_keyboard[this->shift][i], this->qs->text.afilter) || _keyboard[this->shift][i] == ' ');
90 }
91 this->SetWidgetDisabledState(WID_OSK_SPACE, !IsValidChar(' ', this->qs->text.afilter));
92
93 this->SetWidgetLoweredState(WID_OSK_SHIFT, HasBit(_keystate, KEYS_SHIFT));
94 this->SetWidgetLoweredState(WID_OSK_CAPS, HasBit(_keystate, KEYS_CAPS));
95 }
96
97 virtual void SetStringParameters(int widget) const
98 {
99 if (widget == WID_OSK_CAPTION) SetDParam(0, this->caption);
100 }
101
102 virtual void DrawWidget(const Rect &r, int widget) const
103 {
104 if (widget < WID_OSK_LETTERS) return;
105
106 widget -= WID_OSK_LETTERS;
107 DrawCharCentered(_keyboard[this->shift][widget],
108 r.left + 8,
109 r.top + 3,
110 TC_BLACK);
111 }
112
113 virtual void OnClick(Point pt, int widget, int click_count)
114 {
115 /* clicked a letter */
116 if (widget >= WID_OSK_LETTERS) {
117 WChar c = _keyboard[this->shift][widget - WID_OSK_LETTERS];
118
119 if (!IsValidChar(c, this->qs->text.afilter)) return;
120
121 if (this->qs->text.InsertChar(c)) this->OnEditboxChanged(WID_OSK_TEXT);
122
123 if (HasBit(_keystate, KEYS_SHIFT)) {
124 ToggleBit(_keystate, KEYS_SHIFT);
125 this->UpdateOskState();
126 this->SetDirty();
127 }
128 return;
129 }
130
131 switch (widget) {
132 case WID_OSK_BACKSPACE:
133 if (this->qs->text.DeleteChar(WKC_BACKSPACE)) this->OnEditboxChanged(WID_OSK_TEXT);
134 break;
135
136 case WID_OSK_SPECIAL:
137 /*
138 * Anything device specific can go here.
139 * The button itself is hidden by default, and when you need it you
140 * can not hide it in the create event.
141 */
142 break;
143
144 case WID_OSK_CAPS:
145 ToggleBit(_keystate, KEYS_CAPS);
146 this->UpdateOskState();
147 this->SetDirty();
148 break;
149
150 case WID_OSK_SHIFT:
151 ToggleBit(_keystate, KEYS_SHIFT);
152 this->UpdateOskState();
153 this->SetDirty();
154 break;
155
156 case WID_OSK_SPACE:
157 if (this->qs->text.InsertChar(' ')) this->OnEditboxChanged(WID_OSK_TEXT);
158 break;
159
160 case WID_OSK_LEFT:
161 if (this->qs->text.MovePos(WKC_LEFT)) this->InvalidateData();
162 break;
163
164 case WID_OSK_RIGHT:
165 if (this->qs->text.MovePos(WKC_RIGHT)) this->InvalidateData();
166 break;
167
168 case WID_OSK_OK:
169 if (this->qs->orig == NULL || strcmp(this->qs->text.buf, this->qs->orig) != 0) {
170 /* pass information by simulating a button press on parent window */
171 if (this->qs->ok_button >= 0) {
172 this->parent->OnClick(pt, this->qs->ok_button, 1);
173 /* Window gets deleted when the parent window removes itself. */
174 return;
175 }
176 }
177 delete this;
178 break;
179
180 case WID_OSK_CANCEL:
181 if (this->qs->cancel_button >= 0) { // pass a cancel event to the parent window
182 this->parent->OnClick(pt, this->qs->cancel_button, 1);
183 /* Window gets deleted when the parent window removes itself. */
184 return;
185 } else { // or reset to original string
186 qs->text.Assign(this->orig_str_buf);
187 qs->text.MovePos(WKC_END);
188 this->OnEditboxChanged(WID_OSK_TEXT);
189 delete this;
190 }
191 break;
192 }
193 }
194
195 virtual void OnEditboxChanged(int widget)
196 {
197 this->SetWidgetDirty(WID_OSK_TEXT);
198 this->parent->OnEditboxChanged(this->text_btn);
199 this->parent->SetWidgetDirty(this->text_btn);
200 }
201
202 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
203 {
204 if (!gui_scope) return;
205 this->SetWidgetDirty(WID_OSK_TEXT);
206 this->parent->SetWidgetDirty(this->text_btn);
207 }
208
209 virtual void OnFocusLost()
210 {
211 VideoDriver::GetInstance()->EditBoxLostFocus();
212 delete this;
213 }
214 };
215
216 static const int HALF_KEY_WIDTH = 7; // Width of 1/2 key in pixels.
217 static const int INTER_KEY_SPACE = 2; // Number of pixels between two keys.
218
230 static void AddKey(NWidgetHorizontal *hor, int height, int num_half, WidgetType widtype, int widnum, uint16 widdata, int *biggest_index)
231 {
232 int key_width = HALF_KEY_WIDTH + (INTER_KEY_SPACE + HALF_KEY_WIDTH) * (num_half - 1);
233
234 if (widtype == NWID_SPACER) {
235 if (!hor->IsEmpty()) key_width += INTER_KEY_SPACE;
236 NWidgetSpacer *spc = new NWidgetSpacer(key_width, height);
237 hor->Add(spc);
238 } else {
239 if (!hor->IsEmpty()) {
240 NWidgetSpacer *spc = new NWidgetSpacer(INTER_KEY_SPACE, height);
241 hor->Add(spc);
242 }
243 NWidgetLeaf *leaf = new NWidgetLeaf(widtype, COLOUR_GREY, widnum, widdata, STR_NULL);
244 leaf->SetMinimalSize(key_width, height);
245 hor->Add(leaf);
246 }
247
248 *biggest_index = max(*biggest_index, widnum);
249 }
250
252 static NWidgetBase *MakeTopKeys(int *biggest_index)
253 {
254 NWidgetHorizontal *hor = new NWidgetHorizontal();
255 int key_height = FONT_HEIGHT_NORMAL + 2;
256
257 AddKey(hor, key_height, 6 * 2, WWT_TEXTBTN, WID_OSK_CANCEL, STR_BUTTON_CANCEL, biggest_index);
258 AddKey(hor, key_height, 6 * 2, WWT_TEXTBTN, WID_OSK_OK, STR_BUTTON_OK, biggest_index);
259 AddKey(hor, key_height, 2 * 2, WWT_PUSHIMGBTN, WID_OSK_BACKSPACE, SPR_OSK_BACKSPACE, biggest_index);
260 return hor;
261 }
262
264 static NWidgetBase *MakeNumberKeys(int *biggest_index)
265 {
266 NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
267 int key_height = FONT_HEIGHT_NORMAL + 6;
268
269 for (int widnum = WID_OSK_NUMBERS_FIRST; widnum <= WID_OSK_NUMBERS_LAST; widnum++) {
270 AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
271 }
272 return hor;
273 }
274
276 static NWidgetBase *MakeQwertyKeys(int *biggest_index)
277 {
278 NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
279 int key_height = FONT_HEIGHT_NORMAL + 6;
280
281 AddKey(hor, key_height, 3, WWT_PUSHIMGBTN, WID_OSK_SPECIAL, SPR_OSK_SPECIAL, biggest_index);
282 for (int widnum = WID_OSK_QWERTY_FIRST; widnum <= WID_OSK_QWERTY_LAST; widnum++) {
283 AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
284 }
285 AddKey(hor, key_height, 1, NWID_SPACER, 0, 0, biggest_index);
286 return hor;
287 }
288
290 static NWidgetBase *MakeAsdfgKeys(int *biggest_index)
291 {
292 NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
293 int key_height = FONT_HEIGHT_NORMAL + 6;
294
295 AddKey(hor, key_height, 4, WWT_IMGBTN, WID_OSK_CAPS, SPR_OSK_CAPS, biggest_index);
296 for (int widnum = WID_OSK_ASDFG_FIRST; widnum <= WID_OSK_ASDFG_LAST; widnum++) {
297 AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
298 }
299 return hor;
300 }
301
303 static NWidgetBase *MakeZxcvbKeys(int *biggest_index)
304 {
305 NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
306 int key_height = FONT_HEIGHT_NORMAL + 6;
307
308 AddKey(hor, key_height, 3, WWT_IMGBTN, WID_OSK_SHIFT, SPR_OSK_SHIFT, biggest_index);
309 for (int widnum = WID_OSK_ZXCVB_FIRST; widnum <= WID_OSK_ZXCVB_LAST; widnum++) {
310 AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
311 }
312 AddKey(hor, key_height, 1, NWID_SPACER, 0, 0, biggest_index);
313 return hor;
314 }
315
317 static NWidgetBase *MakeSpacebarKeys(int *biggest_index)
318 {
319 NWidgetHorizontal *hor = new NWidgetHorizontal();
320 int key_height = FONT_HEIGHT_NORMAL + 6;
321
322 AddKey(hor, key_height, 8, NWID_SPACER, 0, 0, biggest_index);
323 AddKey(hor, key_height, 13, WWT_PUSHTXTBTN, WID_OSK_SPACE, STR_EMPTY, biggest_index);
324 AddKey(hor, key_height, 3, NWID_SPACER, 0, 0, biggest_index);
325 AddKey(hor, key_height, 2, WWT_PUSHIMGBTN, WID_OSK_LEFT, SPR_OSK_LEFT, biggest_index);
326 AddKey(hor, key_height, 2, WWT_PUSHIMGBTN, WID_OSK_RIGHT, SPR_OSK_RIGHT, biggest_index);
327 return hor;
328 }
329
330
331 static const NWidgetPart _nested_osk_widgets[] = {
332 NWidget(WWT_CAPTION, COLOUR_GREY, WID_OSK_CAPTION), SetDataTip(STR_WHITE_STRING, STR_NULL),
333 NWidget(WWT_PANEL, COLOUR_GREY),
334 NWidget(WWT_EDITBOX, COLOUR_GREY, WID_OSK_TEXT), SetMinimalSize(252, 12), SetPadding(2, 2, 2, 2),
335 EndContainer(),
336 NWidget(WWT_PANEL, COLOUR_GREY), SetPIP(5, 2, 3),
337 NWidgetFunction(MakeTopKeys), SetPadding(0, 3, 0, 3),
338 NWidgetFunction(MakeNumberKeys), SetPadding(0, 3, 0, 3),
339 NWidgetFunction(MakeQwertyKeys), SetPadding(0, 3, 0, 3),
340 NWidgetFunction(MakeAsdfgKeys), SetPadding(0, 3, 0, 3),
341 NWidgetFunction(MakeZxcvbKeys), SetPadding(0, 3, 0, 3),
342 NWidgetFunction(MakeSpacebarKeys), SetPadding(0, 3, 0, 3),
343 EndContainer(),
344 };
345
346 static WindowDesc _osk_desc(
347 WDP_CENTER, "query_osk", 0, 0,
348 WC_OSK, WC_NONE,
349 0,
350 _nested_osk_widgets, lengthof(_nested_osk_widgets)
351 );
352
357 void GetKeyboardLayout()
358 {
359 char keyboard[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
360 char errormark[2][OSK_KEYBOARD_ENTRIES + 1]; // used for marking invalid chars
361 bool has_error = false; // true when an invalid char is detected
362
363 if (StrEmpty(_keyboard_opt[0])) {
364 GetString(keyboard[0], STR_OSK_KEYBOARD_LAYOUT, lastof(keyboard[0]));
365 } else {
366 strecpy(keyboard[0], _keyboard_opt[0], lastof(keyboard[0]));
367 }
368
369 if (StrEmpty(_keyboard_opt[1])) {
370 GetString(keyboard[1], STR_OSK_KEYBOARD_LAYOUT_CAPS, lastof(keyboard[1]));
371 } else {
372 strecpy(keyboard[1], _keyboard_opt[1], lastof(keyboard[1]));
373 }
374
375 for (uint j = 0; j < 2; j++) {
376 const char *kbd = keyboard[j];
377 bool ended = false;
378 for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
379 _keyboard[j][i] = Utf8Consume(&kbd);
380
381 /* Be lenient when the last characters are missing (is quite normal) */
382 if (_keyboard[j][i] == '\0' || ended) {
383 ended = true;
384 _keyboard[j][i] = ' ';
385 continue;
386 }
387
388 if (IsPrintable(_keyboard[j][i])) {
389 errormark[j][i] = ' ';
390 } else {
391 has_error = true;
392 errormark[j][i] = '^';
393 _keyboard[j][i] = ' ';
394 }
395 }
396 }
397
398 if (has_error) {
399 ShowInfoF("The keyboard layout you selected contains invalid chars. Please check those chars marked with ^.");
400 ShowInfoF("Normal keyboard: %s", keyboard[0]);
401 ShowInfoF(" %s", errormark[0]);
402 ShowInfoF("Caps Lock: %s", keyboard[1]);
403 ShowInfoF(" %s", errormark[1]);
404 }
405 }
406
412 void ShowOnScreenKeyboard(Window *parent, int button)
413 {
414 DeleteWindowById(WC_OSK, 0);
415
416 GetKeyboardLayout();
417 new OskWindow(&_osk_desc, parent, button);
418 }
419
427 void UpdateOSKOriginalText(const Window *parent, int button)
428 {
429 OskWindow *osk = dynamic_cast<OskWindow *>(FindWindowById(WC_OSK, 0));
430 if (osk == NULL || osk->parent != parent || osk->text_btn != button) return;
431
432 free(osk->orig_str_buf);
433 osk->orig_str_buf = stredup(osk->qs->text.buf);
434
435 osk->SetDirty();
436 }
437
444 bool IsOSKOpenedFor(const Window *w, int button)
445 {
446 OskWindow *osk = dynamic_cast<OskWindow *>(FindWindowById(WC_OSK, 0));
447 return osk != NULL && osk->parent == w && osk->text_btn == button;
448 }