Fix crash when setting separation mode for vehicles with no orders list.
[openttd-joker.git] / src / osk_gui.cpp
blob99586a54a8ace14ddd3068bb6cbe8882a7e28bf5
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 #include "safeguards.h"
28 char _keyboard_opt[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
29 static WChar _keyboard[2][OSK_KEYBOARD_ENTRIES];
31 enum KeyStateBits {
32 KEYS_NONE,
33 KEYS_SHIFT,
34 KEYS_CAPS
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);
64 this->InitNested(0);
65 this->SetFocusedWidget(WID_OSK_TEXT);
67 /* Not needed by default. */
68 this->DisableWidget(WID_OSK_SPECIAL);
70 this->UpdateOskState();
73 ~OskWindow()
75 free(this->orig_str_buf);
78 /**
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.
83 void UpdateOskState()
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],
108 r.left + 8,
109 r.top + 3,
110 TC_BLACK);
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();
126 this->SetDirty();
128 return;
131 switch (widget) {
132 case WID_OSK_BACKSPACE:
133 if (this->qs->text.DeleteChar(WKC_BACKSPACE)) this->OnEditboxChanged(WID_OSK_TEXT);
134 break;
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.
142 break;
144 case WID_OSK_CAPS:
145 ToggleBit(_keystate, KEYS_CAPS);
146 this->UpdateOskState();
147 this->SetDirty();
148 break;
150 case WID_OSK_SHIFT:
151 ToggleBit(_keystate, KEYS_SHIFT);
152 this->UpdateOskState();
153 this->SetDirty();
154 break;
156 case WID_OSK_SPACE:
157 if (this->qs->text.InsertChar(' ')) this->OnEditboxChanged(WID_OSK_TEXT);
158 break;
160 case WID_OSK_LEFT:
161 if (this->qs->text.MovePos(WKC_LEFT)) this->InvalidateData();
162 break;
164 case WID_OSK_RIGHT:
165 if (this->qs->text.MovePos(WKC_RIGHT)) this->InvalidateData();
166 break;
168 case WID_OSK_OK:
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. */
174 return;
177 delete this;
178 break;
180 case WID_OSK_CANCEL:
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. */
184 return;
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);
189 delete this;
191 break;
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();
212 delete this;
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);
237 hor->Add(spc);
238 } else {
239 if (!hor->IsEmpty()) {
240 NWidgetSpacer *spc = new NWidgetSpacer(INTER_KEY_SPACE, height);
241 hor->Add(spc);
243 NWidgetLeaf *leaf = new NWidgetLeaf(widtype, COLOUR_GREY, widnum, widdata, STR_NULL);
244 leaf->SetMinimalSize(key_width, height);
245 hor->Add(leaf);
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);
260 return hor;
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);
272 return hor;
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);
286 return hor;
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);
299 return hor;
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);
313 return hor;
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);
327 return hor;
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),
335 EndContainer(),
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),
343 EndContainer(),
346 static WindowDesc _osk_desc(
347 WDP_CENTER, "query_osk", 0, 0,
348 WC_OSK, WC_NONE,
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]));
365 } else {
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]));
371 } else {
372 strecpy(keyboard[1], _keyboard_opt[1], lastof(keyboard[1]));
375 for (uint j = 0; j < 2; j++) {
376 const char *kbd = keyboard[j];
377 bool ended = false;
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) {
383 ended = true;
384 _keyboard[j][i] = ' ';
385 continue;
388 if (IsPrintable(_keyboard[j][i])) {
389 errormark[j][i] = ' ';
390 } else {
391 has_error = true;
392 errormark[j][i] = '^';
393 _keyboard[j][i] = ' ';
398 if (has_error) {
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);
416 GetKeyboardLayout();
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);
435 osk->SetDirty();
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;