Add a method in NewGRFInspectWindow to resolve FeatureIndex
[openttd/fttd.git] / src / osk_gui.cpp
blob2516c4dbf63dcda168581c677e6344bfb6dc38c7
1 /* $Id$ */
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 */
10 /** @file osk_gui.cpp The On Screen Keyboard GUI */
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"
21 #include "widgets/osk_widget.h"
23 #include "table/sprites.h"
24 #include "table/strings.h"
26 char _keyboard_opt[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
27 static WChar _keyboard[2][OSK_KEYBOARD_ENTRIES];
29 enum KeyStateBits {
30 KEYS_NONE,
31 KEYS_SHIFT,
32 KEYS_CAPS
34 static byte _keystate = KEYS_NONE;
36 struct OskWindow : public Window {
37 StringID caption; ///< the caption for this window.
38 QueryString *qs; ///< text-input
39 int text_btn; ///< widget number of parent's text field
40 Textbuf *text; ///< pointer to parent's textbuffer (to update caret position)
41 char *orig_str_buf; ///< Original string.
42 bool shift; ///< Is the shift effectively pressed?
44 OskWindow(WindowDesc *desc, Window *parent, int button) : Window(desc)
46 this->parent = parent;
47 assert(parent != NULL);
49 NWidgetCore *par_wid = parent->GetWidget<NWidgetCore>(button);
50 assert(par_wid != NULL);
52 assert(parent->querystrings.Contains(button));
53 this->qs = parent->querystrings.Find(button)->second;
54 this->caption = (par_wid->widget_data != STR_NULL) ? par_wid->widget_data : this->qs->caption;
55 this->text_btn = button;
56 this->text = &this->qs->text;
57 this->querystrings[WID_OSK_TEXT] = this->qs;
59 /* make a copy in case we need to reset later */
60 this->orig_str_buf = strdup(this->qs->text.buf);
62 this->InitNested(0);
63 this->SetFocusedWidget(WID_OSK_TEXT);
65 /* Not needed by default. */
66 this->DisableWidget(WID_OSK_SPECIAL);
68 this->UpdateOskState();
71 ~OskWindow()
73 free(this->orig_str_buf);
76 /**
77 * Only show valid characters; do not show characters that would
78 * only insert a space when we have a spacebar to do that or
79 * characters that are not allowed to be entered.
81 void UpdateOskState()
83 this->shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
85 for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
86 this->SetWidgetDisabledState(WID_OSK_LETTERS + i,
87 !IsValidChar(_keyboard[this->shift][i], this->qs->text.afilter) || _keyboard[this->shift][i] == ' ');
89 this->SetWidgetDisabledState(WID_OSK_SPACE, !IsValidChar(' ', this->qs->text.afilter));
91 this->SetWidgetLoweredState(WID_OSK_SHIFT, HasBit(_keystate, KEYS_SHIFT));
92 this->SetWidgetLoweredState(WID_OSK_CAPS, HasBit(_keystate, KEYS_CAPS));
95 virtual void SetStringParameters(int widget) const
97 if (widget == WID_OSK_CAPTION) SetDParam(0, this->caption);
100 virtual void DrawWidget(const Rect &r, int widget) const
102 if (widget < WID_OSK_LETTERS) return;
104 widget -= WID_OSK_LETTERS;
105 DrawCharCentered(_keyboard[this->shift][widget],
106 r.left + 8,
107 r.top + 3,
108 TC_BLACK);
111 virtual void OnClick(Point pt, int widget, int click_count)
113 /* clicked a letter */
114 if (widget >= WID_OSK_LETTERS) {
115 WChar c = _keyboard[this->shift][widget - WID_OSK_LETTERS];
117 if (!IsValidChar(c, this->qs->text.afilter)) return;
119 if (this->qs->text.InsertChar(c)) this->OnEditboxChanged(WID_OSK_TEXT);
121 if (HasBit(_keystate, KEYS_SHIFT)) {
122 ToggleBit(_keystate, KEYS_SHIFT);
123 this->UpdateOskState();
124 this->SetDirty();
126 return;
129 switch (widget) {
130 case WID_OSK_BACKSPACE:
131 if (this->qs->text.DeleteChar(WKC_BACKSPACE)) this->OnEditboxChanged(WID_OSK_TEXT);
132 break;
134 case WID_OSK_SPECIAL:
136 * Anything device specific can go here.
137 * The button itself is hidden by default, and when you need it you
138 * can not hide it in the create event.
140 break;
142 case WID_OSK_CAPS:
143 ToggleBit(_keystate, KEYS_CAPS);
144 this->UpdateOskState();
145 this->SetDirty();
146 break;
148 case WID_OSK_SHIFT:
149 ToggleBit(_keystate, KEYS_SHIFT);
150 this->UpdateOskState();
151 this->SetDirty();
152 break;
154 case WID_OSK_SPACE:
155 if (this->qs->text.InsertChar(' ')) this->OnEditboxChanged(WID_OSK_TEXT);
156 break;
158 case WID_OSK_LEFT:
159 if (this->qs->text.MovePos(WKC_LEFT)) this->InvalidateData();
160 break;
162 case WID_OSK_RIGHT:
163 if (this->qs->text.MovePos(WKC_RIGHT)) this->InvalidateData();
164 break;
166 case WID_OSK_OK:
167 if (this->qs->orig == NULL || strcmp(this->qs->text.buf, this->qs->orig) != 0) {
168 /* pass information by simulating a button press on parent window */
169 if (this->qs->ok_button >= 0) {
170 this->parent->OnClick(pt, this->qs->ok_button, 1);
171 /* Window gets deleted when the parent window removes itself. */
172 return;
175 delete this;
176 break;
178 case WID_OSK_CANCEL:
179 if (this->qs->cancel_button >= 0) { // pass a cancel event to the parent window
180 this->parent->OnClick(pt, this->qs->cancel_button, 1);
181 /* Window gets deleted when the parent window removes itself. */
182 return;
183 } else { // or reset to original string
184 qs->text.Assign(this->orig_str_buf);
185 qs->text.MovePos(WKC_END);
186 this->OnEditboxChanged(WID_OSK_TEXT);
187 delete this;
189 break;
193 virtual void OnEditboxChanged(int widget)
195 this->SetWidgetDirty(WID_OSK_TEXT);
196 this->parent->OnEditboxChanged(this->text_btn);
197 this->parent->SetWidgetDirty(this->text_btn);
200 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
202 if (!gui_scope) return;
203 this->SetWidgetDirty(WID_OSK_TEXT);
204 this->parent->SetWidgetDirty(this->text_btn);
207 virtual void OnFocusLost()
209 _video_driver->EditBoxLostFocus();
210 delete this;
214 static const int HALF_KEY_WIDTH = 7; // Width of 1/2 key in pixels.
215 static const int INTER_KEY_SPACE = 2; // Number of pixels between two keys.
218 * Add a key widget to a row of the keyboard.
219 * @param hor Row container to add key widget to.
220 * @param height Height of the key (all keys in a row should have equal height).
221 * @param num_half Number of 1/2 key widths that this key has.
222 * @param widtype Widget type of the key. Must be either \c NWID_SPACER for an invisible key, or a \c WWT_* widget.
223 * @param widnum Widget number of the key.
224 * @param widdata Data value of the key widget.
225 * @param biggest_index Collected biggest widget index so far.
226 * @note Key width is measured in 1/2 keys to allow for 1/2 key shifting between rows.
228 static void AddKey(NWidgetHorizontal *hor, int height, int num_half, WidgetType widtype, int widnum, uint16 widdata, int *biggest_index)
230 int key_width = HALF_KEY_WIDTH + (INTER_KEY_SPACE + HALF_KEY_WIDTH) * (num_half - 1);
232 if (widtype == NWID_SPACER) {
233 if (!hor->IsEmpty()) key_width += INTER_KEY_SPACE;
234 NWidgetSpacer *spc = new NWidgetSpacer(key_width, height);
235 hor->Add(spc);
236 } else {
237 if (!hor->IsEmpty()) {
238 NWidgetSpacer *spc = new NWidgetSpacer(INTER_KEY_SPACE, height);
239 hor->Add(spc);
241 NWidgetLeaf *leaf = new NWidgetLeaf(widtype, COLOUR_GREY, widnum, widdata, STR_NULL);
242 leaf->SetMinimalSize(key_width, height);
243 hor->Add(leaf);
246 *biggest_index = max(*biggest_index, widnum);
249 /** Construct the top row keys (cancel, ok, backspace). */
250 static NWidgetBase *MakeTopKeys(int *biggest_index)
252 NWidgetHorizontal *hor = new NWidgetHorizontal();
253 int key_height = FONT_HEIGHT_NORMAL + 2;
255 AddKey(hor, key_height, 6 * 2, WWT_TEXTBTN, WID_OSK_CANCEL, STR_BUTTON_CANCEL, biggest_index);
256 AddKey(hor, key_height, 6 * 2, WWT_TEXTBTN, WID_OSK_OK, STR_BUTTON_OK, biggest_index);
257 AddKey(hor, key_height, 2 * 2, WWT_PUSHIMGBTN, WID_OSK_BACKSPACE, SPR_OSK_BACKSPACE, biggest_index);
258 return hor;
261 /** Construct the row containing the digit keys. */
262 static NWidgetBase *MakeNumberKeys(int *biggest_index)
264 NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
265 int key_height = FONT_HEIGHT_NORMAL + 6;
267 for (int widnum = WID_OSK_NUMBERS_FIRST; widnum <= WID_OSK_NUMBERS_LAST; widnum++) {
268 AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
270 return hor;
273 /** Construct the qwerty row keys. */
274 static NWidgetBase *MakeQwertyKeys(int *biggest_index)
276 NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
277 int key_height = FONT_HEIGHT_NORMAL + 6;
279 AddKey(hor, key_height, 3, WWT_PUSHIMGBTN, WID_OSK_SPECIAL, SPR_OSK_SPECIAL, biggest_index);
280 for (int widnum = WID_OSK_QWERTY_FIRST; widnum <= WID_OSK_QWERTY_LAST; widnum++) {
281 AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
283 AddKey(hor, key_height, 1, NWID_SPACER, 0, 0, biggest_index);
284 return hor;
287 /** Construct the asdfg row keys. */
288 static NWidgetBase *MakeAsdfgKeys(int *biggest_index)
290 NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
291 int key_height = FONT_HEIGHT_NORMAL + 6;
293 AddKey(hor, key_height, 4, WWT_IMGBTN, WID_OSK_CAPS, SPR_OSK_CAPS, biggest_index);
294 for (int widnum = WID_OSK_ASDFG_FIRST; widnum <= WID_OSK_ASDFG_LAST; widnum++) {
295 AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
297 return hor;
300 /** Construct the zxcvb row keys. */
301 static NWidgetBase *MakeZxcvbKeys(int *biggest_index)
303 NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
304 int key_height = FONT_HEIGHT_NORMAL + 6;
306 AddKey(hor, key_height, 3, WWT_IMGBTN, WID_OSK_SHIFT, SPR_OSK_SHIFT, biggest_index);
307 for (int widnum = WID_OSK_ZXCVB_FIRST; widnum <= WID_OSK_ZXCVB_LAST; widnum++) {
308 AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
310 AddKey(hor, key_height, 1, NWID_SPACER, 0, 0, biggest_index);
311 return hor;
314 /** Construct the spacebar row keys. */
315 static NWidgetBase *MakeSpacebarKeys(int *biggest_index)
317 NWidgetHorizontal *hor = new NWidgetHorizontal();
318 int key_height = FONT_HEIGHT_NORMAL + 6;
320 AddKey(hor, key_height, 8, NWID_SPACER, 0, 0, biggest_index);
321 AddKey(hor, key_height, 13, WWT_PUSHTXTBTN, WID_OSK_SPACE, STR_EMPTY, biggest_index);
322 AddKey(hor, key_height, 3, NWID_SPACER, 0, 0, biggest_index);
323 AddKey(hor, key_height, 2, WWT_PUSHIMGBTN, WID_OSK_LEFT, SPR_OSK_LEFT, biggest_index);
324 AddKey(hor, key_height, 2, WWT_PUSHIMGBTN, WID_OSK_RIGHT, SPR_OSK_RIGHT, biggest_index);
325 return hor;
329 static const NWidgetPart _nested_osk_widgets[] = {
330 NWidget(WWT_CAPTION, COLOUR_GREY, WID_OSK_CAPTION), SetDataTip(STR_WHITE_STRING, STR_NULL),
331 NWidget(WWT_PANEL, COLOUR_GREY),
332 NWidget(WWT_EDITBOX, COLOUR_GREY, WID_OSK_TEXT), SetMinimalSize(252, 12), SetPadding(2, 2, 2, 2),
333 EndContainer(),
334 NWidget(WWT_PANEL, COLOUR_GREY), SetPIP(5, 2, 3),
335 NWidgetFunction(MakeTopKeys), SetPadding(0, 3, 0, 3),
336 NWidgetFunction(MakeNumberKeys), SetPadding(0, 3, 0, 3),
337 NWidgetFunction(MakeQwertyKeys), SetPadding(0, 3, 0, 3),
338 NWidgetFunction(MakeAsdfgKeys), SetPadding(0, 3, 0, 3),
339 NWidgetFunction(MakeZxcvbKeys), SetPadding(0, 3, 0, 3),
340 NWidgetFunction(MakeSpacebarKeys), SetPadding(0, 3, 0, 3),
341 EndContainer(),
344 static WindowDesc _osk_desc(
345 WDP_CENTER, "query_osk", 0, 0,
346 WC_OSK, WC_NONE,
348 _nested_osk_widgets, lengthof(_nested_osk_widgets)
352 * Retrieve keyboard layout from language string or (if set) config file.
353 * Also check for invalid characters.
355 void GetKeyboardLayout()
357 char keyboard[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
358 char errormark[2][OSK_KEYBOARD_ENTRIES + 1]; // used for marking invalid chars
359 bool has_error = false; // true when an invalid char is detected
361 if (StrEmpty(_keyboard_opt[0])) {
362 GetString(keyboard[0], STR_OSK_KEYBOARD_LAYOUT, lastof(keyboard[0]));
363 } else {
364 strecpy(keyboard[0], _keyboard_opt[0], lastof(keyboard[0]));
367 if (StrEmpty(_keyboard_opt[1])) {
368 GetString(keyboard[1], STR_OSK_KEYBOARD_LAYOUT_CAPS, lastof(keyboard[1]));
369 } else {
370 strecpy(keyboard[1], _keyboard_opt[1], lastof(keyboard[1]));
373 for (uint j = 0; j < 2; j++) {
374 const char *kbd = keyboard[j];
375 bool ended = false;
376 for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
377 _keyboard[j][i] = Utf8Consume(&kbd);
379 /* Be lenient when the last characters are missing (is quite normal) */
380 if (_keyboard[j][i] == '\0' || ended) {
381 ended = true;
382 _keyboard[j][i] = ' ';
383 continue;
386 if (IsPrintable(_keyboard[j][i])) {
387 errormark[j][i] = ' ';
388 } else {
389 has_error = true;
390 errormark[j][i] = '^';
391 _keyboard[j][i] = ' ';
396 if (has_error) {
397 ShowInfoF("The keyboard layout you selected contains invalid chars. Please check those chars marked with ^.");
398 ShowInfoF("Normal keyboard: %s", keyboard[0]);
399 ShowInfoF(" %s", errormark[0]);
400 ShowInfoF("Caps Lock: %s", keyboard[1]);
401 ShowInfoF(" %s", errormark[1]);
406 * Show the on-screen keyboard (osk) associated with a given textbox
407 * @param parent pointer to the Window where this keyboard originated from
408 * @param button widget number of parent's textbox
410 void ShowOnScreenKeyboard(Window *parent, int button)
412 DeleteWindowById(WC_OSK, 0);
414 GetKeyboardLayout();
415 new OskWindow(&_osk_desc, parent, button);
419 * Updates the original text of the OSK so when the 'parent' changes the
420 * original and you press on cancel you won't get the 'old' original text
421 * but the updated one.
422 * @param parent window that just updated its orignal text
423 * @param button widget number of parent's textbox to update
425 void UpdateOSKOriginalText(const Window *parent, int button)
427 OskWindow *osk = dynamic_cast<OskWindow *>(FindWindowById(WC_OSK, 0));
428 if (osk == NULL || osk->parent != parent || osk->text_btn != button) return;
430 free(osk->orig_str_buf);
431 osk->orig_str_buf = strdup(osk->qs->text.buf);
433 osk->SetDirty();
437 * Check whether the OSK is opened for a specific editbox.
438 * @parent w Window to check for
439 * @param button Editbox of \a w to check for
440 * @return true if the OSK is oppened for \a button.
442 bool IsOSKOpenedFor(const Window *w, int button)
444 OskWindow *osk = dynamic_cast<OskWindow *>(FindWindowById(WC_OSK, 0));
445 return osk != NULL && osk->parent == w && osk->text_btn == button;