Remove ZeroedMemoryAllocator as a base class of Window
[openttd/fttd.git] / src / osk_gui.cpp
blobb53c8cc4b08cc2a3b8223ec9e6b3707973f60894
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.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 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());
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->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],
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->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();
124 this->SetDirty();
126 return;
129 switch (widget) {
130 case WID_OSK_BACKSPACE:
131 if (this->qs->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->InsertChar(' ')) this->OnEditboxChanged(WID_OSK_TEXT);
156 break;
158 case WID_OSK_LEFT:
159 if (this->qs->MovePos(WKC_LEFT)) this->InvalidateData();
160 break;
162 case WID_OSK_RIGHT:
163 if (this->qs->MovePos(WKC_RIGHT)) this->InvalidateData();
164 break;
166 case WID_OSK_OK:
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. */
172 return;
175 this->Delete();
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->Assign(this->orig_str_buf);
185 qs->MovePos(WKC_END);
186 this->OnEditboxChanged(WID_OSK_TEXT);
187 this->Delete();
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 VideoDriver::GetActiveDriver()->EditBoxLostFocus();
210 this->Delete();
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::Prefs _osk_prefs ("query_osk");
346 static const WindowDesc _osk_desc(
347 WDP_CENTER, 0, 0,
348 WC_OSK, WC_NONE,
350 _nested_osk_widgets, lengthof(_nested_osk_widgets),
351 &_osk_prefs
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);
366 } else {
367 bstrcpy (keyboard[0], _keyboard_opt[0]);
370 if (StrEmpty(_keyboard_opt[1])) {
371 GetString (keyboard[1], STR_OSK_KEYBOARD_LAYOUT_CAPS);
372 } else {
373 bstrcpy (keyboard[1], _keyboard_opt[1]);
376 for (uint j = 0; j < 2; j++) {
377 const char *kbd = keyboard[j];
378 bool ended = false;
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) {
384 ended = true;
385 _keyboard[j][i] = ' ';
386 continue;
389 if (IsPrintable(_keyboard[j][i])) {
390 errormark[j][i] = ' ';
391 } else {
392 has_error = true;
393 errormark[j][i] = '^';
394 _keyboard[j][i] = ' ';
399 if (has_error) {
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);
417 GetKeyboardLayout();
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());
436 osk->SetDirty();
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;