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 */
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 char _keyboard_opt
[2][OSK_KEYBOARD_ENTRIES
* 4 + 1];
27 static WChar _keyboard
[2][OSK_KEYBOARD_ENTRIES
];
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 char *orig_str_buf
; ///< Original string.
41 bool shift
; ///< Is the shift effectively pressed?
43 OskWindow (const WindowDesc
*desc
, Window
*parent
, int button
) :
44 Window (desc
), caption (STR_NULL
), qs (NULL
), text_btn (0),
45 orig_str_buf (NULL
), shift (false)
47 this->parent
= parent
;
48 assert(parent
!= NULL
);
50 NWidgetCore
*par_wid
= parent
->GetWidget
<NWidgetCore
>(button
);
51 assert(par_wid
!= NULL
);
53 assert(parent
->querystrings
.Contains(button
));
54 this->qs
= parent
->querystrings
.Find(button
)->second
;
55 this->caption
= (par_wid
->widget_data
!= STR_NULL
) ? par_wid
->widget_data
: this->qs
->caption
;
56 this->text_btn
= button
;
57 this->querystrings
[WID_OSK_TEXT
] = this->qs
;
59 /* make a copy in case we need to reset later */
60 this->orig_str_buf
= xstrdup(this->qs
->GetText());
63 this->SetFocusedWidget(WID_OSK_TEXT
);
65 /* Not needed by default. */
66 this->DisableWidget(WID_OSK_SPECIAL
);
68 this->UpdateOskState();
73 free(this->orig_str_buf
);
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.
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
->afilter
) || _keyboard
[this->shift
][i
] == ' ');
89 this->SetWidgetDisabledState(WID_OSK_SPACE
, !IsValidChar(' ', this->qs
->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
],
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
->afilter
)) return;
119 if (this->qs
->InsertChar(c
)) this->OnEditboxChanged(WID_OSK_TEXT
);
121 if (HasBit(_keystate
, KEYS_SHIFT
)) {
122 ToggleBit(_keystate
, KEYS_SHIFT
);
123 this->UpdateOskState();
130 case WID_OSK_BACKSPACE
:
131 if (this->qs
->DeleteChar(WKC_BACKSPACE
)) this->OnEditboxChanged(WID_OSK_TEXT
);
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.
143 ToggleBit(_keystate
, KEYS_CAPS
);
144 this->UpdateOskState();
149 ToggleBit(_keystate
, KEYS_SHIFT
);
150 this->UpdateOskState();
155 if (this->qs
->InsertChar(' ')) this->OnEditboxChanged(WID_OSK_TEXT
);
159 if (this->qs
->MovePos(WKC_LEFT
)) this->InvalidateData();
163 if (this->qs
->MovePos(WKC_RIGHT
)) this->InvalidateData();
167 if (this->qs
->orig
== NULL
|| strcmp(this->qs
->GetText(), 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. */
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. */
183 } else { // or reset to original string
184 qs
->Assign(this->orig_str_buf
);
185 qs
->MovePos(WKC_END
);
186 this->OnEditboxChanged(WID_OSK_TEXT
);
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 VideoDriver::GetActiveDriver()->EditBoxLostFocus();
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
);
237 if (!hor
->IsEmpty()) {
238 NWidgetSpacer
*spc
= new NWidgetSpacer(INTER_KEY_SPACE
, height
);
241 NWidgetLeaf
*leaf
= new NWidgetLeaf(widtype
, COLOUR_GREY
, widnum
, widdata
, STR_NULL
);
242 leaf
->SetMinimalSize(key_width
, height
);
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
);
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
);
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
);
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
);
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
);
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
);
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),
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),
344 static WindowDesc::Prefs
_osk_prefs ("query_osk");
346 static const WindowDesc
_osk_desc(
350 _nested_osk_widgets
, lengthof(_nested_osk_widgets
),
355 * Retrieve keyboard layout from language string or (if set) config file.
356 * Also check for invalid characters.
358 void GetKeyboardLayout()
360 char keyboard
[2][OSK_KEYBOARD_ENTRIES
* 4 + 1];
361 char errormark
[2][OSK_KEYBOARD_ENTRIES
+ 1]; // used for marking invalid chars
362 bool has_error
= false; // true when an invalid char is detected
364 if (StrEmpty(_keyboard_opt
[0])) {
365 GetString (keyboard
[0], STR_OSK_KEYBOARD_LAYOUT
);
367 bstrcpy (keyboard
[0], _keyboard_opt
[0]);
370 if (StrEmpty(_keyboard_opt
[1])) {
371 GetString (keyboard
[1], STR_OSK_KEYBOARD_LAYOUT_CAPS
);
373 bstrcpy (keyboard
[1], _keyboard_opt
[1]);
376 for (uint j
= 0; j
< 2; j
++) {
377 const char *kbd
= keyboard
[j
];
379 for (uint i
= 0; i
< OSK_KEYBOARD_ENTRIES
; i
++) {
380 _keyboard
[j
][i
] = Utf8Consume(&kbd
);
382 /* Be lenient when the last characters are missing (is quite normal) */
383 if (_keyboard
[j
][i
] == '\0' || ended
) {
385 _keyboard
[j
][i
] = ' ';
389 if (IsPrintable(_keyboard
[j
][i
])) {
390 errormark
[j
][i
] = ' ';
393 errormark
[j
][i
] = '^';
394 _keyboard
[j
][i
] = ' ';
400 ShowInfoF("The keyboard layout you selected contains invalid chars. Please check those chars marked with ^.");
401 ShowInfoF("Normal keyboard: %s", keyboard
[0]);
402 ShowInfoF(" %s", errormark
[0]);
403 ShowInfoF("Caps Lock: %s", keyboard
[1]);
404 ShowInfoF(" %s", errormark
[1]);
409 * Show the on-screen keyboard (osk) associated with a given textbox
410 * @param parent pointer to the Window where this keyboard originated from
411 * @param button widget number of parent's textbox
413 void ShowOnScreenKeyboard(Window
*parent
, int button
)
415 DeleteWindowById(WC_OSK
, 0);
418 new OskWindow(&_osk_desc
, parent
, button
);
422 * Updates the original text of the OSK so when the 'parent' changes the
423 * original and you press on cancel you won't get the 'old' original text
424 * but the updated one.
425 * @param parent window that just updated its orignal text
426 * @param button widget number of parent's textbox to update
428 void UpdateOSKOriginalText(const Window
*parent
, int button
)
430 OskWindow
*osk
= dynamic_cast<OskWindow
*>(FindWindowById(WC_OSK
, 0));
431 if (osk
== NULL
|| osk
->parent
!= parent
|| osk
->text_btn
!= button
) return;
433 free(osk
->orig_str_buf
);
434 osk
->orig_str_buf
= xstrdup(osk
->qs
->GetText());
440 * Check whether the OSK is opened for a specific editbox.
441 * @parent w Window to check for
442 * @param button Editbox of \a w to check for
443 * @return true if the OSK is oppened for \a button.
445 bool IsOSKOpenedFor(const Window
*w
, int button
)
447 OskWindow
*osk
= dynamic_cast<OskWindow
*>(FindWindowById(WC_OSK
, 0));
448 return osk
!= NULL
&& osk
->parent
== w
&& osk
->text_btn
== button
;