Translations update
[openttd/fttd.git] / src / osk_gui.cpp
blobe73d42b9f8b3c503232a8c8b81cad7295ebcfc6e
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 "core/pointer.h"
14 #include "string.h"
15 #include "strings_func.h"
16 #include "debug.h"
17 #include "window_func.h"
18 #include "gfx_func.h"
19 #include "querystring_gui.h"
20 #include "video/video_driver.hpp"
22 #include "widgets/osk_widget.h"
24 #include "table/sprites.h"
25 #include "table/strings.h"
27 char _keyboard_opt[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
28 static WChar _keyboard[2][OSK_KEYBOARD_ENTRIES];
30 enum KeyStateBits {
31 KEYS_NONE,
32 KEYS_SHIFT,
33 KEYS_CAPS
35 static byte _keystate = KEYS_NONE;
37 struct OskWindow : public Window {
38 StringID caption; ///< the caption for this window.
39 QueryString *qs; ///< text-input
40 int text_btn; ///< widget number of parent's text field
41 ttd_unique_free_ptr<char> orig_str_buf; ///< Original string.
42 bool shift; ///< Is the shift effectively pressed?
44 OskWindow (const WindowDesc *desc, Window *parent, int button) :
45 Window (desc), caption (STR_NULL), qs (NULL), text_btn (0),
46 orig_str_buf (NULL), shift (false)
48 this->parent = parent;
49 assert(parent != NULL);
51 NWidgetCore *par_wid = parent->GetWidget<NWidgetCore>(button);
52 assert(par_wid != NULL);
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->querystrings[WID_OSK_TEXT] = this->qs;
60 /* make a copy in case we need to reset later */
61 this->orig_str_buf.reset (xstrdup (this->qs->GetText()));
63 this->InitNested(0);
64 this->SetFocusedWidget(WID_OSK_TEXT);
66 /* Not needed by default. */
67 this->DisableWidget(WID_OSK_SPECIAL);
69 this->UpdateOskState();
72 /**
73 * Only show valid characters; do not show characters that would
74 * only insert a space when we have a spacebar to do that or
75 * characters that are not allowed to be entered.
77 void UpdateOskState()
79 this->shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
81 for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
82 this->SetWidgetDisabledState(WID_OSK_LETTERS + i,
83 !IsValidChar(_keyboard[this->shift][i], this->qs->afilter) || _keyboard[this->shift][i] == ' ');
85 this->SetWidgetDisabledState(WID_OSK_SPACE, !IsValidChar(' ', this->qs->afilter));
87 this->SetWidgetLoweredState(WID_OSK_SHIFT, HasBit(_keystate, KEYS_SHIFT));
88 this->SetWidgetLoweredState(WID_OSK_CAPS, HasBit(_keystate, KEYS_CAPS));
91 virtual void SetStringParameters(int widget) const
93 if (widget == WID_OSK_CAPTION) SetDParam(0, this->caption);
96 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
98 if (widget < WID_OSK_LETTERS) return;
100 widget -= WID_OSK_LETTERS;
101 DrawCharCentered (dpi, _keyboard[this->shift][widget],
102 r.left + 8,
103 r.top + 3,
104 TC_BLACK);
107 virtual void OnClick(Point pt, int widget, int click_count)
109 /* clicked a letter */
110 if (widget >= WID_OSK_LETTERS) {
111 WChar c = _keyboard[this->shift][widget - WID_OSK_LETTERS];
113 if (!IsValidChar(c, this->qs->afilter)) return;
115 if (this->qs->InsertChar(c)) this->OnEditboxChanged(WID_OSK_TEXT);
117 if (HasBit(_keystate, KEYS_SHIFT)) {
118 ToggleBit(_keystate, KEYS_SHIFT);
119 this->UpdateOskState();
120 this->SetDirty();
122 return;
125 switch (widget) {
126 case WID_OSK_BACKSPACE:
127 if (this->qs->DeleteChar()) this->OnEditboxChanged(WID_OSK_TEXT);
128 break;
130 case WID_OSK_SPECIAL:
132 * Anything device specific can go here.
133 * The button itself is hidden by default, and when you need it you
134 * can not hide it in the create event.
136 break;
138 case WID_OSK_CAPS:
139 ToggleBit(_keystate, KEYS_CAPS);
140 this->UpdateOskState();
141 this->SetDirty();
142 break;
144 case WID_OSK_SHIFT:
145 ToggleBit(_keystate, KEYS_SHIFT);
146 this->UpdateOskState();
147 this->SetDirty();
148 break;
150 case WID_OSK_SPACE:
151 if (this->qs->InsertChar(' ')) this->OnEditboxChanged(WID_OSK_TEXT);
152 break;
154 case WID_OSK_LEFT:
155 if (this->qs->MoveLeft()) this->InvalidateData();
156 break;
158 case WID_OSK_RIGHT:
159 if (this->qs->MoveRight()) this->InvalidateData();
160 break;
162 case WID_OSK_OK:
163 if (!this->qs->orig || strcmp (this->qs->GetText(), this->qs->orig.get()) != 0) {
164 /* pass information by simulating a button press on parent window */
165 if (this->qs->ok_button >= 0) {
166 this->parent->OnClick(pt, this->qs->ok_button, 1);
167 /* Window gets deleted when the parent window removes itself. */
168 return;
171 this->Delete();
172 break;
174 case WID_OSK_CANCEL:
175 if (this->qs->cancel_button >= 0) { // pass a cancel event to the parent window
176 this->parent->OnClick(pt, this->qs->cancel_button, 1);
177 /* Window gets deleted when the parent window removes itself. */
178 return;
179 } else { // or reset to original string
180 qs->Assign(this->orig_str_buf.get());
181 qs->MoveEnd();
182 this->OnEditboxChanged(WID_OSK_TEXT);
183 this->Delete();
185 break;
189 virtual void OnEditboxChanged(int widget)
191 this->SetWidgetDirty(WID_OSK_TEXT);
192 this->parent->OnEditboxChanged(this->text_btn);
193 this->parent->SetWidgetDirty(this->text_btn);
196 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
198 if (!gui_scope) return;
199 this->SetWidgetDirty(WID_OSK_TEXT);
200 this->parent->SetWidgetDirty(this->text_btn);
203 virtual void OnFocusLost()
205 VideoDriver::GetActiveDriver()->EditBoxLostFocus();
209 static const int HALF_KEY_WIDTH = 7; // Width of 1/2 key in pixels.
210 static const int INTER_KEY_SPACE = 2; // Number of pixels between two keys.
213 * Add a key widget to a row of the keyboard.
214 * @param hor Row container to add key widget to.
215 * @param height Height of the key (all keys in a row should have equal height).
216 * @param num_half Number of 1/2 key widths that this key has.
217 * @param widtype Widget type of the key. Must be either \c NWID_SPACER for an invisible key, or a \c WWT_* widget.
218 * @param widnum Widget number of the key.
219 * @param widdata Data value of the key widget.
220 * @param biggest_index Collected biggest widget index so far.
221 * @note Key width is measured in 1/2 keys to allow for 1/2 key shifting between rows.
223 static void AddKey(NWidgetHorizontal *hor, int height, int num_half, WidgetType widtype, int widnum, uint16 widdata, int *biggest_index)
225 int key_width = HALF_KEY_WIDTH + (INTER_KEY_SPACE + HALF_KEY_WIDTH) * (num_half - 1);
227 if (widtype == NWID_SPACER) {
228 if (!hor->IsEmpty()) key_width += INTER_KEY_SPACE;
229 NWidgetSpacer *spc = new NWidgetSpacer(key_width, height);
230 hor->Add(spc);
231 } else {
232 if (!hor->IsEmpty()) {
233 NWidgetSpacer *spc = new NWidgetSpacer(INTER_KEY_SPACE, height);
234 hor->Add(spc);
236 NWidgetLeaf *leaf = new NWidgetLeaf(widtype, COLOUR_GREY, widnum, widdata, STR_NULL);
237 leaf->SetMinimalSize(key_width, height);
238 hor->Add(leaf);
241 *biggest_index = max(*biggest_index, widnum);
244 /** Construct the top row keys (cancel, ok, backspace). */
245 static NWidgetBase *MakeTopKeys(int *biggest_index)
247 NWidgetHorizontal *hor = new NWidgetHorizontal();
248 int key_height = FONT_HEIGHT_NORMAL + 2;
250 AddKey(hor, key_height, 6 * 2, WWT_TEXTBTN, WID_OSK_CANCEL, STR_BUTTON_CANCEL, biggest_index);
251 AddKey(hor, key_height, 6 * 2, WWT_TEXTBTN, WID_OSK_OK, STR_BUTTON_OK, biggest_index);
252 AddKey(hor, key_height, 2 * 2, WWT_PUSHIMGBTN, WID_OSK_BACKSPACE, SPR_OSK_BACKSPACE, biggest_index);
253 return hor;
256 /** Construct the row containing the digit keys. */
257 static NWidgetBase *MakeNumberKeys(int *biggest_index)
259 NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
260 int key_height = FONT_HEIGHT_NORMAL + 6;
262 for (int widnum = WID_OSK_NUMBERS_FIRST; widnum <= WID_OSK_NUMBERS_LAST; widnum++) {
263 AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
265 return hor;
268 /** Construct the qwerty row keys. */
269 static NWidgetBase *MakeQwertyKeys(int *biggest_index)
271 NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
272 int key_height = FONT_HEIGHT_NORMAL + 6;
274 AddKey(hor, key_height, 3, WWT_PUSHIMGBTN, WID_OSK_SPECIAL, SPR_OSK_SPECIAL, biggest_index);
275 for (int widnum = WID_OSK_QWERTY_FIRST; widnum <= WID_OSK_QWERTY_LAST; widnum++) {
276 AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
278 AddKey(hor, key_height, 1, NWID_SPACER, 0, 0, biggest_index);
279 return hor;
282 /** Construct the asdfg row keys. */
283 static NWidgetBase *MakeAsdfgKeys(int *biggest_index)
285 NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
286 int key_height = FONT_HEIGHT_NORMAL + 6;
288 AddKey(hor, key_height, 4, WWT_IMGBTN, WID_OSK_CAPS, SPR_OSK_CAPS, biggest_index);
289 for (int widnum = WID_OSK_ASDFG_FIRST; widnum <= WID_OSK_ASDFG_LAST; widnum++) {
290 AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
292 return hor;
295 /** Construct the zxcvb row keys. */
296 static NWidgetBase *MakeZxcvbKeys(int *biggest_index)
298 NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
299 int key_height = FONT_HEIGHT_NORMAL + 6;
301 AddKey(hor, key_height, 3, WWT_IMGBTN, WID_OSK_SHIFT, SPR_OSK_SHIFT, biggest_index);
302 for (int widnum = WID_OSK_ZXCVB_FIRST; widnum <= WID_OSK_ZXCVB_LAST; widnum++) {
303 AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
305 AddKey(hor, key_height, 1, NWID_SPACER, 0, 0, biggest_index);
306 return hor;
309 /** Construct the spacebar row keys. */
310 static NWidgetBase *MakeSpacebarKeys(int *biggest_index)
312 NWidgetHorizontal *hor = new NWidgetHorizontal();
313 int key_height = FONT_HEIGHT_NORMAL + 6;
315 AddKey(hor, key_height, 8, NWID_SPACER, 0, 0, biggest_index);
316 AddKey(hor, key_height, 13, WWT_PUSHTXTBTN, WID_OSK_SPACE, STR_EMPTY, biggest_index);
317 AddKey(hor, key_height, 3, NWID_SPACER, 0, 0, biggest_index);
318 AddKey(hor, key_height, 2, WWT_PUSHIMGBTN, WID_OSK_LEFT, SPR_OSK_LEFT, biggest_index);
319 AddKey(hor, key_height, 2, WWT_PUSHIMGBTN, WID_OSK_RIGHT, SPR_OSK_RIGHT, biggest_index);
320 return hor;
324 static const NWidgetPart _nested_osk_widgets[] = {
325 NWidget(WWT_CAPTION, COLOUR_GREY, WID_OSK_CAPTION), SetDataTip(STR_WHITE_STRING, STR_NULL),
326 NWidget(WWT_PANEL, COLOUR_GREY),
327 NWidget(WWT_EDITBOX, COLOUR_GREY, WID_OSK_TEXT), SetMinimalSize(252, 12), SetPadding(2, 2, 2, 2),
328 EndContainer(),
329 NWidget(WWT_PANEL, COLOUR_GREY), SetPIP(5, 2, 3),
330 NWidgetFunction(MakeTopKeys), SetPadding(0, 3, 0, 3),
331 NWidgetFunction(MakeNumberKeys), SetPadding(0, 3, 0, 3),
332 NWidgetFunction(MakeQwertyKeys), SetPadding(0, 3, 0, 3),
333 NWidgetFunction(MakeAsdfgKeys), SetPadding(0, 3, 0, 3),
334 NWidgetFunction(MakeZxcvbKeys), SetPadding(0, 3, 0, 3),
335 NWidgetFunction(MakeSpacebarKeys), SetPadding(0, 3, 0, 3),
336 EndContainer(),
339 static WindowDesc::Prefs _osk_prefs ("query_osk");
341 static const WindowDesc _osk_desc(
342 WDP_CENTER, 0, 0,
343 WC_OSK, WC_NONE,
345 _nested_osk_widgets, lengthof(_nested_osk_widgets),
346 &_osk_prefs
350 * Retrieve keyboard layout from language string or (if set) config file.
351 * Also check for invalid characters.
353 void GetKeyboardLayout()
355 char keyboard[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
356 char errormark[2][OSK_KEYBOARD_ENTRIES + 1]; // used for marking invalid chars
357 bool has_error = false; // true when an invalid char is detected
359 if (StrEmpty(_keyboard_opt[0])) {
360 GetString (keyboard[0], STR_OSK_KEYBOARD_LAYOUT);
361 } else {
362 bstrcpy (keyboard[0], _keyboard_opt[0]);
365 if (StrEmpty(_keyboard_opt[1])) {
366 GetString (keyboard[1], STR_OSK_KEYBOARD_LAYOUT_CAPS);
367 } else {
368 bstrcpy (keyboard[1], _keyboard_opt[1]);
371 for (uint j = 0; j < 2; j++) {
372 const char *kbd = keyboard[j];
373 bool ended = false;
374 for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
375 _keyboard[j][i] = Utf8Consume(&kbd);
377 /* Be lenient when the last characters are missing (is quite normal) */
378 if (_keyboard[j][i] == '\0' || ended) {
379 ended = true;
380 _keyboard[j][i] = ' ';
381 continue;
384 if (IsPrintable(_keyboard[j][i])) {
385 errormark[j][i] = ' ';
386 } else {
387 has_error = true;
388 errormark[j][i] = '^';
389 _keyboard[j][i] = ' ';
394 if (has_error) {
395 ShowInfoF("The keyboard layout you selected contains invalid chars. Please check those chars marked with ^.");
396 ShowInfoF("Normal keyboard: %s", keyboard[0]);
397 ShowInfoF(" %s", errormark[0]);
398 ShowInfoF("Caps Lock: %s", keyboard[1]);
399 ShowInfoF(" %s", errormark[1]);
404 * Show the on-screen keyboard (osk) associated with a given textbox
405 * @param parent pointer to the Window where this keyboard originated from
406 * @param button widget number of parent's textbox
408 void ShowOnScreenKeyboard(Window *parent, int button)
410 DeleteWindowById(WC_OSK, 0);
412 GetKeyboardLayout();
413 new OskWindow(&_osk_desc, parent, button);
417 * Updates the original text of the OSK so when the 'parent' changes the
418 * original and you press on cancel you won't get the 'old' original text
419 * but the updated one.
420 * @param parent window that just updated its orignal text
421 * @param button widget number of parent's textbox to update
423 void UpdateOSKOriginalText(const Window *parent, int button)
425 OskWindow *osk = dynamic_cast<OskWindow *>(FindWindowById(WC_OSK, 0));
426 if (osk == NULL || osk->parent != parent || osk->text_btn != button) return;
428 osk->orig_str_buf.reset (xstrdup (osk->qs->GetText()));
430 osk->SetDirty();
434 * Check whether the OSK is opened for a specific editbox.
435 * @parent w Window to check for
436 * @param button Editbox of \a w to check for
437 * @return true if the OSK is oppened for \a button.
439 bool IsOSKOpenedFor(const Window *w, int button)
441 OskWindow *osk = dynamic_cast<OskWindow *>(FindWindowById(WC_OSK, 0));
442 return osk != NULL && osk->parent == w && osk->text_btn == button;