src/osk_gui.cpp
changeset 9233 4daa9bb8dbf7
child 9235 02e09ffa89dc
equal deleted inserted replaced
9232:37291ba2aa2a 9233:4daa9bb8dbf7
       
     1 /* $Id$ */
       
     2 
       
     3 /** @file osk_gui.cpp The On Screen Keyboard GUI */
       
     4 
       
     5 #include "stdafx.h"
       
     6 #include "openttd.h"
       
     7 
       
     8 #include "textbuf_gui.h"
       
     9 #include "window_gui.h"
       
    10 #include "string_func.h"
       
    11 #include "strings_func.h"
       
    12 #include "debug.h"
       
    13 #include "window_func.h"
       
    14 #include "gfx_func.h"
       
    15 
       
    16 #include "table/sprites.h"
       
    17 #include "table/strings.h"
       
    18 
       
    19 struct osk_d {
       
    20 	querystr_d *qs; // text-input
       
    21 	int text_btn;   // widget number of parent's text field
       
    22 	int ok_btn;     // widget number of parent's ok button (=0 when ok shouldn't be passed on)
       
    23 	int cancel_btn; // widget number of parent's cancel button (=0 when cancel shouldn't be passed on; text will be reverted to original)
       
    24 	Textbuf *text;  // pointer to parent's textbuffer (to update caret position)
       
    25 	char *orig;     // the original text, in case we cancel
       
    26 };
       
    27 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(osk_d));
       
    28 
       
    29 enum OskWidgets {
       
    30 	OSK_WIDGET_TEXT = 3,
       
    31 	OSK_WIDGET_CANCEL = 5,
       
    32 	OSK_WIDGET_OK,
       
    33 	OSK_WIDGET_BACKSPACE,
       
    34 	OSK_WIDGET_SPECIAL,
       
    35 	OSK_WIDGET_CAPS,
       
    36 	OSK_WIDGET_SHIFT,
       
    37 	OSK_WIDGET_SPACE,
       
    38 	OSK_WIDGET_LEFT,
       
    39 	OSK_WIDGET_RIGHT,
       
    40 	OSK_WIDGET_LETTERS
       
    41 };
       
    42 
       
    43 char _keyboard_opt[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
       
    44 static WChar _keyboard[2][OSK_KEYBOARD_ENTRIES];
       
    45 
       
    46 enum {
       
    47 	KEYS_NONE,
       
    48 	KEYS_SHIFT,
       
    49 	KEYS_CAPS
       
    50 };
       
    51 static byte _keystate = KEYS_NONE;
       
    52 
       
    53 /*
       
    54  * Only show valid characters; do not show characters that would
       
    55  * only insert a space when we have a spacebar to do that or
       
    56  * characters that are not allowed to be entered.
       
    57  */
       
    58 static void ChangeOskDiabledState(Window *w, const querystr_d *qs, bool shift)
       
    59 {
       
    60 	for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
       
    61 		w->SetWidgetDisabledState(OSK_WIDGET_LETTERS + i,
       
    62 				!IsValidChar(_keyboard[shift][i], qs->afilter) || _keyboard[shift][i] == ' ');
       
    63 	}
       
    64 	w->SetWidgetDisabledState(OSK_WIDGET_SPACE, !IsValidChar(' ', qs->afilter));
       
    65 }
       
    66 
       
    67 /* on screen keyboard */
       
    68 static void OskWndProc(Window *w, WindowEvent *e)
       
    69 {
       
    70 	querystr_d *qs = WP(w, osk_d).qs;
       
    71 
       
    72 	switch (e->event) {
       
    73 		case WE_CREATE:
       
    74 			SetBit(_no_scroll, SCROLL_EDIT);
       
    75 			/* Not needed by default. */
       
    76 			w->DisableWidget(OSK_WIDGET_SPECIAL);
       
    77 			break;
       
    78 
       
    79 		case WE_PAINT: {
       
    80 			bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
       
    81 
       
    82 			w->LowerWidget(OSK_WIDGET_TEXT);
       
    83 			w->SetWidgetLoweredState(OSK_WIDGET_SHIFT, HasBit(_keystate, KEYS_SHIFT));
       
    84 			w->SetWidgetLoweredState(OSK_WIDGET_CAPS, HasBit(_keystate, KEYS_CAPS));
       
    85 
       
    86 			ChangeOskDiabledState(w, qs, shift);
       
    87 
       
    88 			SetDParam(0, qs->caption);
       
    89 			DrawWindowWidgets(w);
       
    90 
       
    91 			for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
       
    92 				DrawCharCentered(_keyboard[shift][i],
       
    93 					w->widget[OSK_WIDGET_LETTERS + i].left + 8,
       
    94 					w->widget[OSK_WIDGET_LETTERS + i].top + 3,
       
    95 					TC_BLACK);
       
    96 			}
       
    97 
       
    98 			DrawEditBox(w, qs, OSK_WIDGET_TEXT);
       
    99 			break;
       
   100 		}
       
   101 
       
   102 		case WE_CLICK:
       
   103 			/* clicked a letter */
       
   104 			if (e->we.click.widget >= OSK_WIDGET_LETTERS) {
       
   105 				bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
       
   106 
       
   107 				WChar c = _keyboard[shift][e->we.click.widget - OSK_WIDGET_LETTERS];
       
   108 
       
   109 				if (!IsValidChar(c, qs->afilter)) break;
       
   110 
       
   111 				if (InsertTextBufferChar(&qs->text, c)) w->InvalidateWidget(OSK_WIDGET_TEXT);
       
   112 
       
   113 				if (HasBit(_keystate, KEYS_SHIFT)) {
       
   114 					ToggleBit(_keystate, KEYS_SHIFT);
       
   115 					w->widget[OSK_WIDGET_SHIFT].color = HasBit(_keystate, KEYS_SHIFT) ? 15 : 14;
       
   116 					SetWindowDirty(w);
       
   117 				}
       
   118 				break;
       
   119 			}
       
   120 
       
   121 			switch (e->we.click.widget) {
       
   122 				case OSK_WIDGET_BACKSPACE:
       
   123 					if (DeleteTextBufferChar(&qs->text, WKC_BACKSPACE)) w->InvalidateWidget(OSK_WIDGET_TEXT);
       
   124 					break;
       
   125 
       
   126 				case OSK_WIDGET_SPECIAL:
       
   127 					/*
       
   128 					 * Anything device specific can go here.
       
   129 					 * The button itself is hidden by default, and when you need it you
       
   130 					 * can not hide it in the create event.
       
   131 					 */
       
   132 					break;
       
   133 
       
   134 				case OSK_WIDGET_CAPS:
       
   135 					ToggleBit(_keystate, KEYS_CAPS);
       
   136 					SetWindowDirty(w);
       
   137 					break;
       
   138 
       
   139 				case OSK_WIDGET_SHIFT:
       
   140 					ToggleBit(_keystate, KEYS_SHIFT);
       
   141 					SetWindowDirty(w);
       
   142 					break;
       
   143 
       
   144 				case OSK_WIDGET_SPACE:
       
   145 					if (InsertTextBufferChar(&qs->text, ' ')) w->InvalidateWidget(OSK_WIDGET_TEXT);
       
   146 					break;
       
   147 
       
   148 				case OSK_WIDGET_LEFT:
       
   149 					if (MoveTextBufferPos(&qs->text, WKC_LEFT)) w->InvalidateWidget(OSK_WIDGET_TEXT);
       
   150 					break;
       
   151 
       
   152 				case OSK_WIDGET_RIGHT:
       
   153 					if (MoveTextBufferPos(&qs->text, WKC_RIGHT)) w->InvalidateWidget(OSK_WIDGET_TEXT);
       
   154 					break;
       
   155 
       
   156 				case OSK_WIDGET_OK:
       
   157 					if (qs->orig == NULL || strcmp(qs->text.buf, qs->orig) != 0) {
       
   158 						/* pass information by simulating a button press on parent window */
       
   159 						if (WP(w, osk_d).ok_btn != 0) {
       
   160 							Window *parent = w->parent;
       
   161 							WindowEvent e;
       
   162 							e.event = WE_CLICK;
       
   163 							e.we.click.widget = WP(w, osk_d).ok_btn;
       
   164 							parent->wndproc(parent, &e);
       
   165 						}
       
   166 					}
       
   167 					DeleteWindow(w);
       
   168 					break;
       
   169 
       
   170 				case OSK_WIDGET_CANCEL:
       
   171 					if (WP(w, osk_d).cancel_btn != 0) { // pass a cancel event to the parent window
       
   172 						Window *parent = w->parent;
       
   173 						WindowEvent e;
       
   174 						e.event = WE_CLICK;
       
   175 						e.we.click.widget = WP(w, osk_d).cancel_btn;
       
   176 						parent->wndproc(parent, &e);
       
   177 					} else { // or reset to original string
       
   178 						strcpy(qs->text.buf, WP(w, osk_d).orig);
       
   179 						UpdateTextBufferSize(&qs->text);
       
   180 						MoveTextBufferPos(&qs->text, WKC_END);
       
   181 					}
       
   182 					DeleteWindow(w);
       
   183 					break;
       
   184 			}
       
   185 			/* make sure that the parent window's textbox also gets updated */
       
   186 			if (w->parent != NULL) w->parent->InvalidateWidget(WP(w, osk_d).text_btn);
       
   187 			break;
       
   188 
       
   189 		case WE_MOUSELOOP:
       
   190 			HandleEditBox(w, qs, OSK_WIDGET_TEXT);
       
   191 			/* make the caret of the parent window also blink */
       
   192 			w->parent->InvalidateWidget(WP(w, osk_d).text_btn);
       
   193 			break;
       
   194 	}
       
   195 }
       
   196 
       
   197 static const Widget _osk_widgets[] = {
       
   198 {      WWT_EMPTY, RESIZE_NONE,     0,     0,     0,     0,     0, 0x0,               STR_NULL},
       
   199 {    WWT_CAPTION, RESIZE_NONE,    14,     0,   255,     0,    13, STR_012D,          STR_NULL},
       
   200 {      WWT_PANEL, RESIZE_NONE,    14,     0,   255,    14,    29, 0x0,               STR_NULL},
       
   201 {    WWT_EDITBOX, RESIZE_NONE,    14,     2,   253,    16,    27, 0x0,               STR_NULL},
       
   202 
       
   203 {      WWT_PANEL, RESIZE_NONE,    14,     0,   255,    30,   139, 0x0,               STR_NULL},
       
   204 
       
   205 {    WWT_TEXTBTN, RESIZE_NONE,    14,     3,   108,    35,    46, STR_012E_CANCEL,   STR_NULL},
       
   206 {    WWT_TEXTBTN, RESIZE_NONE,    14,   111,   216,    35,    46, STR_012F_OK,       STR_NULL},
       
   207 { WWT_PUSHIMGBTN, RESIZE_NONE,    14,   219,   252,    35,    46, SPR_OSK_BACKSPACE, STR_NULL},
       
   208 
       
   209 { WWT_PUSHIMGBTN, RESIZE_NONE,    14,     3,    27,    67,    82, SPR_OSK_SPECIAL,   STR_NULL},
       
   210 {     WWT_IMGBTN, RESIZE_NONE,    14,     3,    36,    85,   100, SPR_OSK_CAPS,      STR_NULL},
       
   211 {     WWT_IMGBTN, RESIZE_NONE,    14,     3,    27,   103,   118, SPR_OSK_SHIFT,     STR_NULL},
       
   212 
       
   213 { WWT_PUSHTXTBTN, RESIZE_NONE,    14,    75,   189,   121,   136, STR_EMPTY,         STR_NULL},
       
   214 
       
   215 { WWT_PUSHIMGBTN, RESIZE_NONE,    14,   219,   234,   121,   136, SPR_OSK_LEFT,      STR_NULL},
       
   216 { WWT_PUSHIMGBTN, RESIZE_NONE,    14,   237,   252,   121,   136, SPR_OSK_RIGHT,     STR_NULL},
       
   217 
       
   218 {    WWT_PUSHBTN, RESIZE_NONE,    14,     3,    18,    49,    64, 0x0,    STR_NULL},
       
   219 {    WWT_PUSHBTN, RESIZE_NONE,    14,    21,    36,    49,    64, 0x0,    STR_NULL},
       
   220 {    WWT_PUSHBTN, RESIZE_NONE,    14,    39,    54,    49,    64, 0x0,    STR_NULL},
       
   221 {    WWT_PUSHBTN, RESIZE_NONE,    14,    57,    72,    49,    64, 0x0,    STR_NULL},
       
   222 {    WWT_PUSHBTN, RESIZE_NONE,    14,    75,    90,    49,    64, 0x0,    STR_NULL},
       
   223 {    WWT_PUSHBTN, RESIZE_NONE,    14,    93,   108,    49,    64, 0x0,    STR_NULL},
       
   224 {    WWT_PUSHBTN, RESIZE_NONE,    14,   111,   126,    49,    64, 0x0,    STR_NULL},
       
   225 {    WWT_PUSHBTN, RESIZE_NONE,    14,   129,   144,    49,    64, 0x0,    STR_NULL},
       
   226 {    WWT_PUSHBTN, RESIZE_NONE,    14,   147,   162,    49,    64, 0x0,    STR_NULL},
       
   227 {    WWT_PUSHBTN, RESIZE_NONE,    14,   165,   180,    49,    64, 0x0,    STR_NULL},
       
   228 {    WWT_PUSHBTN, RESIZE_NONE,    14,   183,   198,    49,    64, 0x0,    STR_NULL},
       
   229 {    WWT_PUSHBTN, RESIZE_NONE,    14,   201,   216,    49,    64, 0x0,    STR_NULL},
       
   230 {    WWT_PUSHBTN, RESIZE_NONE,    14,   219,   234,    49,    64, 0x0,    STR_NULL},
       
   231 {    WWT_PUSHBTN, RESIZE_NONE,    14,   237,   252,    49,    64, 0x0,    STR_NULL},
       
   232 
       
   233 {    WWT_PUSHBTN, RESIZE_NONE,    14,    30,    45,    67,    82, 0x0,    STR_NULL},
       
   234 {    WWT_PUSHBTN, RESIZE_NONE,    14,    48,    63,    67,    82, 0x0,    STR_NULL},
       
   235 {    WWT_PUSHBTN, RESIZE_NONE,    14,    66,    81,    67,    82, 0x0,    STR_NULL},
       
   236 {    WWT_PUSHBTN, RESIZE_NONE,    14,    84,    99,    67,    82, 0x0,    STR_NULL},
       
   237 {    WWT_PUSHBTN, RESIZE_NONE,    14,   102,   117,    67,    82, 0x0,    STR_NULL},
       
   238 {    WWT_PUSHBTN, RESIZE_NONE,    14,   120,   135,    67,    82, 0x0,    STR_NULL},
       
   239 {    WWT_PUSHBTN, RESIZE_NONE,    14,   138,   153,    67,    82, 0x0,    STR_NULL},
       
   240 {    WWT_PUSHBTN, RESIZE_NONE,    14,   156,   171,    67,    82, 0x0,    STR_NULL},
       
   241 {    WWT_PUSHBTN, RESIZE_NONE,    14,   174,   189,    67,    82, 0x0,    STR_NULL},
       
   242 {    WWT_PUSHBTN, RESIZE_NONE,    14,   192,   207,    67,    82, 0x0,    STR_NULL},
       
   243 {    WWT_PUSHBTN, RESIZE_NONE,    14,   210,   225,    67,    82, 0x0,    STR_NULL},
       
   244 {    WWT_PUSHBTN, RESIZE_NONE,    14,   228,   243,    67,    82, 0x0,    STR_NULL},
       
   245 
       
   246 {    WWT_PUSHBTN, RESIZE_NONE,    14,    39,    54,    85,   100, 0x0,    STR_NULL},
       
   247 {    WWT_PUSHBTN, RESIZE_NONE,    14,    57,    72,    85,   100, 0x0,    STR_NULL},
       
   248 {    WWT_PUSHBTN, RESIZE_NONE,    14,    75,    90,    85,   100, 0x0,    STR_NULL},
       
   249 {    WWT_PUSHBTN, RESIZE_NONE,    14,    93,   108,    85,   100, 0x0,    STR_NULL},
       
   250 {    WWT_PUSHBTN, RESIZE_NONE,    14,   111,   126,    85,   100, 0x0,    STR_NULL},
       
   251 {    WWT_PUSHBTN, RESIZE_NONE,    14,   129,   144,    85,   100, 0x0,    STR_NULL},
       
   252 {    WWT_PUSHBTN, RESIZE_NONE,    14,   147,   162,    85,   100, 0x0,    STR_NULL},
       
   253 {    WWT_PUSHBTN, RESIZE_NONE,    14,   165,   180,    85,   100, 0x0,    STR_NULL},
       
   254 {    WWT_PUSHBTN, RESIZE_NONE,    14,   183,   198,    85,   100, 0x0,    STR_NULL},
       
   255 {    WWT_PUSHBTN, RESIZE_NONE,    14,   201,   216,    85,   100, 0x0,    STR_NULL},
       
   256 {    WWT_PUSHBTN, RESIZE_NONE,    14,   219,   234,    85,   100, 0x0,    STR_NULL},
       
   257 {    WWT_PUSHBTN, RESIZE_NONE,    14,   237,   252,    85,   100, 0x0,    STR_NULL},
       
   258 
       
   259 {    WWT_PUSHBTN, RESIZE_NONE,    14,    30,    45,   103,   118, 0x0,    STR_NULL},
       
   260 {    WWT_PUSHBTN, RESIZE_NONE,    14,    48,    63,   103,   118, 0x0,    STR_NULL},
       
   261 {    WWT_PUSHBTN, RESIZE_NONE,    14,    66,    81,   103,   118, 0x0,    STR_NULL},
       
   262 {    WWT_PUSHBTN, RESIZE_NONE,    14,    84,    99,   103,   118, 0x0,    STR_NULL},
       
   263 {    WWT_PUSHBTN, RESIZE_NONE,    14,   102,   117,   103,   118, 0x0,    STR_NULL},
       
   264 {    WWT_PUSHBTN, RESIZE_NONE,    14,   120,   135,   103,   118, 0x0,    STR_NULL},
       
   265 {    WWT_PUSHBTN, RESIZE_NONE,    14,   138,   153,   103,   118, 0x0,    STR_NULL},
       
   266 {    WWT_PUSHBTN, RESIZE_NONE,    14,   156,   171,   103,   118, 0x0,    STR_NULL},
       
   267 {    WWT_PUSHBTN, RESIZE_NONE,    14,   174,   189,   103,   118, 0x0,    STR_NULL},
       
   268 {    WWT_PUSHBTN, RESIZE_NONE,    14,   192,   207,   103,   118, 0x0,    STR_NULL},
       
   269 {    WWT_PUSHBTN, RESIZE_NONE,    14,   210,   225,   103,   118, 0x0,    STR_NULL},
       
   270 {    WWT_PUSHBTN, RESIZE_NONE,    14,   228,   243,   103,   118, 0x0,    STR_NULL},
       
   271 
       
   272 {   WIDGETS_END},
       
   273 };
       
   274 
       
   275 WindowDesc _osk_desc = {
       
   276 	WDP_CENTER, WDP_CENTER, 256, 140, 256, 140,
       
   277 	WC_OSK, WC_NONE,
       
   278 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
       
   279 	_osk_widgets,
       
   280 	OskWndProc
       
   281 };
       
   282 
       
   283 /**
       
   284  * Retrieve keyboard layout from language string or (if set) config file.
       
   285  * Also check for invalid characters.
       
   286  */
       
   287 void GetKeyboardLayout()
       
   288 {
       
   289 	char keyboard[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
       
   290 	char errormark[2][OSK_KEYBOARD_ENTRIES + 1]; // used for marking invalid chars
       
   291 	bool has_error = false; // true when an invalid char is detected
       
   292 
       
   293 	if (StrEmpty(_keyboard_opt[0])) {
       
   294 		GetString(keyboard[0], STR_OSK_KEYBOARD_LAYOUT, lastof(keyboard[0]));
       
   295 	} else {
       
   296 		strncpy(keyboard[0], _keyboard_opt[0], lengthof(keyboard[0]));
       
   297 	}
       
   298 
       
   299 	if (StrEmpty(_keyboard_opt[1])) {
       
   300 		GetString(keyboard[1], STR_OSK_KEYBOARD_LAYOUT_CAPS, lastof(keyboard[1]));
       
   301 	} else {
       
   302 		strncpy(keyboard[0], _keyboard_opt[0], lengthof(keyboard[1]));
       
   303 	}
       
   304 
       
   305 	for (uint j = 0; j < 2; j++) {
       
   306 		const char *kbd = keyboard[j];
       
   307 		for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
       
   308 			_keyboard[j][i] = Utf8Consume(&kbd);
       
   309 
       
   310 			if (IsPrintable(_keyboard[j][i])) {
       
   311 				errormark[j][i] = ' ';
       
   312 			} else {
       
   313 				has_error = true;
       
   314 				errormark[j][i] = '^';
       
   315 				_keyboard[j][i] = ' ';
       
   316 			}
       
   317 		}
       
   318 	}
       
   319 
       
   320 	if (has_error) {
       
   321 		ShowInfoF("The keyboard layout you selected contains invalid chars. Please check those chars marked with ^.");
       
   322 		ShowInfoF("Normal keyboard:  %s", keyboard[0]);
       
   323 		ShowInfoF("                  %s", errormark[0]);
       
   324 		ShowInfoF("Caps Lock:        %s", keyboard[1]);
       
   325 		ShowInfoF("                  %s", errormark[1]);
       
   326 	}
       
   327 }
       
   328 
       
   329 /**
       
   330  * Show the osk associated with a given textbox
       
   331  * @param parent pointer to the Window where this keyboard originated from
       
   332  * @param q      querystr_d pointer to the query string of the parent, which is
       
   333  *               shared for both windows
       
   334  * @param button widget number of parent's textbox
       
   335  * @param cancel widget number of parent's cancel button (0 if cancel events
       
   336  *               should not be passed)
       
   337  * @param ok     widget number of parent's ok button  (0 if ok events should not
       
   338  *               be passed)
       
   339  */
       
   340 void ShowOnScreenKeyboard(Window *parent, querystr_d *q, int button, int cancel, int ok)
       
   341 {
       
   342 	DeleteWindowById(WC_OSK, 0);
       
   343 
       
   344 	Window *w = AllocateWindowDesc(&_osk_desc);
       
   345 
       
   346 	w->parent = parent;
       
   347 	assert(parent != NULL);
       
   348 
       
   349 	if (parent->widget[button].data != 0) q->caption = parent->widget[button].data;
       
   350 
       
   351 	WP(w, osk_d).qs         = q;
       
   352 	WP(w, osk_d).text_btn   = button;
       
   353 	WP(w, osk_d).cancel_btn = cancel;
       
   354 	WP(w, osk_d).ok_btn     = ok;
       
   355 	WP(w, osk_d).text       = &q->text;
       
   356 
       
   357 	GetKeyboardLayout();
       
   358 
       
   359 	/* make a copy in case we need to reset later */
       
   360 	strcpy(_orig_str_buf, WP(w, osk_d).qs->text.buf);
       
   361 	WP(w, osk_d).orig = _orig_str_buf;
       
   362 }