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/>.
10 /** @file osk_gui.cpp The On Screen Keyboard GUI */
13 #include "string_func.h"
14 #include "strings_func.h"
16 #include "window_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 #include "safeguards.h"
28 char _keyboard_opt
[2][OSK_KEYBOARD_ENTRIES
* 4 + 1];
29 static WChar _keyboard
[2][OSK_KEYBOARD_ENTRIES
];
36 static byte _keystate
= KEYS_NONE
;
38 struct OskWindow
: public Window
{
39 StringID caption
; ///< the caption for this window.
40 QueryString
*qs
; ///< text-input
41 int text_btn
; ///< widget number of parent's text field
42 Textbuf
*text
; ///< pointer to parent's textbuffer (to update caret position)
43 char *orig_str_buf
; ///< Original string.
44 bool shift
; ///< Is the shift effectively pressed?
46 OskWindow(WindowDesc
*desc
, Window
*parent
, int button
) : Window(desc
)
48 this->parent
= parent
;
49 assert(parent
!= nullptr);
51 NWidgetCore
*par_wid
= parent
->GetWidget
<NWidgetCore
>(button
);
52 assert(par_wid
!= nullptr);
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
;
61 /* make a copy in case we need to reset later */
62 this->orig_str_buf
= stredup(this->qs
->text
.buf
);
65 this->SetFocusedWidget(WID_OSK_TEXT
);
67 /* Not needed by default. */
68 this->DisableWidget(WID_OSK_SPECIAL
);
70 this->UpdateOskState();
75 free(this->orig_str_buf
);
79 * Only show valid characters; do not show characters that would
80 * only insert a space when we have a spacebar to do that or
81 * characters that are not allowed to be entered.
85 this->shift
= HasBit(_keystate
, KEYS_CAPS
) ^ HasBit(_keystate
, KEYS_SHIFT
);
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
] == ' ');
91 this->SetWidgetDisabledState(WID_OSK_SPACE
, !IsValidChar(' ', this->qs
->text
.afilter
));
93 this->SetWidgetLoweredState(WID_OSK_SHIFT
, HasBit(_keystate
, KEYS_SHIFT
));
94 this->SetWidgetLoweredState(WID_OSK_CAPS
, HasBit(_keystate
, KEYS_CAPS
));
97 virtual void SetStringParameters(int widget
) const
99 if (widget
== WID_OSK_CAPTION
) SetDParam(0, this->caption
);
102 virtual void DrawWidget(const Rect
&r
, int widget
) const
104 if (widget
< WID_OSK_LETTERS
) return;
106 widget
-= WID_OSK_LETTERS
;
107 DrawCharCentered(_keyboard
[this->shift
][widget
],
113 virtual void OnClick(Point pt
, int widget
, int click_count
)
115 /* clicked a letter */
116 if (widget
>= WID_OSK_LETTERS
) {
117 WChar c
= _keyboard
[this->shift
][widget
- WID_OSK_LETTERS
];
119 if (!IsValidChar(c
, this->qs
->text
.afilter
)) return;
121 if (this->qs
->text
.InsertChar(c
)) this->OnEditboxChanged(WID_OSK_TEXT
);
123 if (HasBit(_keystate
, KEYS_SHIFT
)) {
124 ToggleBit(_keystate
, KEYS_SHIFT
);
125 this->UpdateOskState();
132 case WID_OSK_BACKSPACE
:
133 if (this->qs
->text
.DeleteChar(WKC_BACKSPACE
)) this->OnEditboxChanged(WID_OSK_TEXT
);
136 case WID_OSK_SPECIAL
:
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.
145 ToggleBit(_keystate
, KEYS_CAPS
);
146 this->UpdateOskState();
151 ToggleBit(_keystate
, KEYS_SHIFT
);
152 this->UpdateOskState();
157 if (this->qs
->text
.InsertChar(' ')) this->OnEditboxChanged(WID_OSK_TEXT
);
161 if (this->qs
->text
.MovePos(WKC_LEFT
)) this->InvalidateData();
165 if (this->qs
->text
.MovePos(WKC_RIGHT
)) this->InvalidateData();
169 if (this->qs
->orig
== nullptr || 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. */
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. */
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
);
195 virtual void OnEditboxChanged(int widget
)
197 this->SetWidgetDirty(WID_OSK_TEXT
);
198 this->parent
->OnEditboxChanged(this->text_btn
);
199 this->parent
->SetWidgetDirty(this->text_btn
);
202 virtual void OnInvalidateData(int data
= 0, bool gui_scope
= true)
204 if (!gui_scope
) return;
205 this->SetWidgetDirty(WID_OSK_TEXT
);
206 this->parent
->SetWidgetDirty(this->text_btn
);
209 virtual void OnFocusLost(Window
*newly_focused_window
)
211 VideoDriver::GetInstance()->EditBoxLostFocus();
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.
220 * Add a key widget to a row of the keyboard.
221 * @param hor Row container to add key widget to.
222 * @param height Height of the key (all keys in a row should have equal height).
223 * @param num_half Number of 1/2 key widths that this key has.
224 * @param widtype Widget type of the key. Must be either \c NWID_SPACER for an invisible key, or a \c WWT_* widget.
225 * @param widnum Widget number of the key.
226 * @param widdata Data value of the key widget.
227 * @param biggest_index Collected biggest widget index so far.
228 * @note Key width is measured in 1/2 keys to allow for 1/2 key shifting between rows.
230 static void AddKey(NWidgetHorizontal
*hor
, int height
, int num_half
, WidgetType widtype
, int widnum
, uint16 widdata
, int *biggest_index
)
232 int key_width
= HALF_KEY_WIDTH
+ (INTER_KEY_SPACE
+ HALF_KEY_WIDTH
) * (num_half
- 1);
234 if (widtype
== NWID_SPACER
) {
235 if (!hor
->IsEmpty()) key_width
+= INTER_KEY_SPACE
;
236 NWidgetSpacer
*spc
= new NWidgetSpacer(key_width
, height
);
239 if (!hor
->IsEmpty()) {
240 NWidgetSpacer
*spc
= new NWidgetSpacer(INTER_KEY_SPACE
, height
);
243 NWidgetLeaf
*leaf
= new NWidgetLeaf(widtype
, COLOUR_GREY
, widnum
, widdata
, STR_NULL
);
244 leaf
->SetMinimalSize(key_width
, height
);
248 *biggest_index
= max(*biggest_index
, widnum
);
251 /** Construct the top row keys (cancel, ok, backspace). */
252 static NWidgetBase
*MakeTopKeys(int *biggest_index
)
254 NWidgetHorizontal
*hor
= new NWidgetHorizontal();
255 int key_height
= FONT_HEIGHT_NORMAL
+ 2;
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
);
263 /** Construct the row containing the digit keys. */
264 static NWidgetBase
*MakeNumberKeys(int *biggest_index
)
266 NWidgetHorizontal
*hor
= new NWidgetHorizontalLTR();
267 int key_height
= FONT_HEIGHT_NORMAL
+ 6;
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
);
275 /** Construct the qwerty row keys. */
276 static NWidgetBase
*MakeQwertyKeys(int *biggest_index
)
278 NWidgetHorizontal
*hor
= new NWidgetHorizontalLTR();
279 int key_height
= FONT_HEIGHT_NORMAL
+ 6;
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
);
285 AddKey(hor
, key_height
, 1, NWID_SPACER
, 0, 0, biggest_index
);
289 /** Construct the asdfg row keys. */
290 static NWidgetBase
*MakeAsdfgKeys(int *biggest_index
)
292 NWidgetHorizontal
*hor
= new NWidgetHorizontalLTR();
293 int key_height
= FONT_HEIGHT_NORMAL
+ 6;
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
);
302 /** Construct the zxcvb row keys. */
303 static NWidgetBase
*MakeZxcvbKeys(int *biggest_index
)
305 NWidgetHorizontal
*hor
= new NWidgetHorizontalLTR();
306 int key_height
= FONT_HEIGHT_NORMAL
+ 6;
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
);
312 AddKey(hor
, key_height
, 1, NWID_SPACER
, 0, 0, biggest_index
);
316 /** Construct the spacebar row keys. */
317 static NWidgetBase
*MakeSpacebarKeys(int *biggest_index
)
319 NWidgetHorizontal
*hor
= new NWidgetHorizontal();
320 int key_height
= FONT_HEIGHT_NORMAL
+ 6;
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
);
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),
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),
346 static WindowDesc
_osk_desc(
347 WDP_CENTER
, "query_osk", 0, 0,
350 _nested_osk_widgets
, lengthof(_nested_osk_widgets
)
354 * Retrieve keyboard layout from language string or (if set) config file.
355 * Also check for invalid characters.
357 void GetKeyboardLayout()
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
363 if (StrEmpty(_keyboard_opt
[0])) {
364 GetString(keyboard
[0], STR_OSK_KEYBOARD_LAYOUT
, lastof(keyboard
[0]));
366 strecpy(keyboard
[0], _keyboard_opt
[0], lastof(keyboard
[0]));
369 if (StrEmpty(_keyboard_opt
[1])) {
370 GetString(keyboard
[1], STR_OSK_KEYBOARD_LAYOUT_CAPS
, lastof(keyboard
[1]));
372 strecpy(keyboard
[1], _keyboard_opt
[1], lastof(keyboard
[1]));
375 for (uint j
= 0; j
< 2; j
++) {
376 const char *kbd
= keyboard
[j
];
378 for (uint i
= 0; i
< OSK_KEYBOARD_ENTRIES
; i
++) {
379 _keyboard
[j
][i
] = Utf8Consume(&kbd
);
381 /* Be lenient when the last characters are missing (is quite normal) */
382 if (_keyboard
[j
][i
] == '\0' || ended
) {
384 _keyboard
[j
][i
] = ' ';
388 if (IsPrintable(_keyboard
[j
][i
])) {
389 errormark
[j
][i
] = ' ';
392 errormark
[j
][i
] = '^';
393 _keyboard
[j
][i
] = ' ';
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]);
408 * Show the on-screen keyboard (osk) associated with a given textbox
409 * @param parent pointer to the Window where this keyboard originated from
410 * @param button widget number of parent's textbox
412 void ShowOnScreenKeyboard(Window
*parent
, int button
)
414 DeleteWindowById(WC_OSK
, 0);
417 new OskWindow(&_osk_desc
, parent
, button
);
421 * Updates the original text of the OSK so when the 'parent' changes the
422 * original and you press on cancel you won't get the 'old' original text
423 * but the updated one.
424 * @param parent window that just updated its orignal text
425 * @param button widget number of parent's textbox to update
427 void UpdateOSKOriginalText(const Window
*parent
, int button
)
429 OskWindow
*osk
= dynamic_cast<OskWindow
*>(FindWindowById(WC_OSK
, 0));
430 if (osk
== nullptr || osk
->parent
!= parent
|| osk
->text_btn
!= button
) return;
432 free(osk
->orig_str_buf
);
433 osk
->orig_str_buf
= stredup(osk
->qs
->text
.buf
);
439 * Check whether the OSK is opened for a specific editbox.
440 * @parent w Window to check for
441 * @param button Editbox of \a w to check for
442 * @return true if the OSK is oppened for \a button.
444 bool IsOSKOpenedFor(const Window
*w
, int button
)
446 OskWindow
*osk
= dynamic_cast<OskWindow
*>(FindWindowById(WC_OSK
, 0));
447 return osk
!= nullptr && osk
->parent
== w
&& osk
->text_btn
== button
;