src/osk_gui.cpp
changeset 10499 9c2d9bdea61a
parent 10491 39a3e4dc217f
child 10538 8d2fcedeb9e9
equal deleted inserted replaced
10498:f198d0a1aab7 10499:9c2d9bdea61a
    10 #include "string_func.h"
    10 #include "string_func.h"
    11 #include "strings_func.h"
    11 #include "strings_func.h"
    12 #include "debug.h"
    12 #include "debug.h"
    13 #include "window_func.h"
    13 #include "window_func.h"
    14 #include "gfx_func.h"
    14 #include "gfx_func.h"
       
    15 #include "querystring_gui.h"
    15 
    16 
    16 #include "table/sprites.h"
    17 #include "table/sprites.h"
    17 #include "table/strings.h"
    18 #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 
    19 
    29 enum OskWidgets {
    20 enum OskWidgets {
    30 	OSK_WIDGET_TEXT = 3,
    21 	OSK_WIDGET_TEXT = 3,
    31 	OSK_WIDGET_CANCEL = 5,
    22 	OSK_WIDGET_CANCEL = 5,
    32 	OSK_WIDGET_OK,
    23 	OSK_WIDGET_OK,
    48 	KEYS_SHIFT,
    39 	KEYS_SHIFT,
    49 	KEYS_CAPS
    40 	KEYS_CAPS
    50 };
    41 };
    51 static byte _keystate = KEYS_NONE;
    42 static byte _keystate = KEYS_NONE;
    52 
    43 
    53 /*
    44 struct OskWindow : public Window {
    54  * Only show valid characters; do not show characters that would
    45 	QueryString *qs;       ///< text-input
    55  * only insert a space when we have a spacebar to do that or
    46 	int text_btn;          ///< widget number of parent's text field
    56  * characters that are not allowed to be entered.
    47 	int ok_btn;            ///< widget number of parent's ok button (=0 when ok shouldn't be passed on)
    57  */
    48 	int cancel_btn;        ///< widget number of parent's cancel button (=0 when cancel shouldn't be passed on; text will be reverted to original)
    58 static void ChangeOskDiabledState(Window *w, const querystr_d *qs, bool shift)
    49 	Textbuf *text;         ///< pointer to parent's textbuffer (to update caret position)
    59 {
    50 	char orig_str_buf[64]; ///< Original string.
    60 	for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
    51 
    61 		w->SetWidgetDisabledState(OSK_WIDGET_LETTERS + i,
    52 	OskWindow(const WindowDesc *desc, QueryStringBaseWindow *parent, int button, int cancel, int ok) : Window(desc)
    62 				!IsValidChar(_keyboard[shift][i], qs->afilter) || _keyboard[shift][i] == ' ');
    53 	{
    63 	}
    54 		this->parent = parent;
    64 	w->SetWidgetDisabledState(OSK_WIDGET_SPACE, !IsValidChar(' ', qs->afilter));
    55 		assert(parent != NULL);
    65 }
    56 
    66 
    57 		if (parent->widget[button].data != 0) parent->caption = parent->widget[button].data;
    67 /* on screen keyboard */
    58 
    68 static void OskWndProc(Window *w, WindowEvent *e)
    59 		this->qs         = parent;
    69 {
    60 		this->text_btn   = button;
    70 	querystr_d *qs = WP(w, osk_d).qs;
    61 		this->cancel_btn = cancel;
    71 
    62 		this->ok_btn     = ok;
    72 	switch (e->event) {
    63 		this->text       = &parent->text;
    73 		case WE_CREATE:
    64 
    74 			SetBit(_no_scroll, SCROLL_EDIT);
    65 		/* make a copy in case we need to reset later */
    75 			/* Not needed by default. */
    66 		strcpy(this->orig_str_buf, this->qs->text.buf);
    76 			w->DisableWidget(OSK_WIDGET_SPECIAL);
    67 
    77 			break;
    68 		SetBit(_no_scroll, SCROLL_EDIT);
    78 
    69 		/* Not needed by default. */
    79 		case WE_PAINT: {
    70 		this->DisableWidget(OSK_WIDGET_SPECIAL);
       
    71 
       
    72 		this->FindWindowPlacementAndResize(desc);
       
    73 	}
       
    74 
       
    75 	/**
       
    76 	 * Only show valid characters; do not show characters that would
       
    77 	 * only insert a space when we have a spacebar to do that or
       
    78 	 * characters that are not allowed to be entered.
       
    79 	 */
       
    80 	void ChangeOskDiabledState(bool shift)
       
    81 	{
       
    82 		for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
       
    83 			this->SetWidgetDisabledState(OSK_WIDGET_LETTERS + i,
       
    84 					!IsValidChar(_keyboard[shift][i], this->qs->afilter) || _keyboard[shift][i] == ' ');
       
    85 		}
       
    86 		this->SetWidgetDisabledState(OSK_WIDGET_SPACE, !IsValidChar(' ', this->qs->afilter));
       
    87 	}
       
    88 
       
    89 	virtual void OnPaint()
       
    90 	{
       
    91 		bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
       
    92 
       
    93 		this->LowerWidget(OSK_WIDGET_TEXT);
       
    94 		this->SetWidgetLoweredState(OSK_WIDGET_SHIFT, HasBit(_keystate, KEYS_SHIFT));
       
    95 		this->SetWidgetLoweredState(OSK_WIDGET_CAPS, HasBit(_keystate, KEYS_CAPS));
       
    96 
       
    97 		this->ChangeOskDiabledState(shift);
       
    98 
       
    99 		SetDParam(0, this->qs->caption);
       
   100 		DrawWindowWidgets(this);
       
   101 
       
   102 		for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
       
   103 			DrawCharCentered(_keyboard[shift][i],
       
   104 				this->widget[OSK_WIDGET_LETTERS + i].left + 8,
       
   105 				this->widget[OSK_WIDGET_LETTERS + i].top + 3,
       
   106 				TC_BLACK);
       
   107 		}
       
   108 
       
   109 		this->qs->DrawEditBox(this, OSK_WIDGET_TEXT);
       
   110 	}
       
   111 
       
   112 	virtual void OnClick(Point pt, int widget)
       
   113 	{
       
   114 		/* clicked a letter */
       
   115 		if (widget >= OSK_WIDGET_LETTERS) {
    80 			bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
   116 			bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
    81 
   117 
    82 			w->LowerWidget(OSK_WIDGET_TEXT);
   118 			WChar c = _keyboard[shift][widget - OSK_WIDGET_LETTERS];
    83 			w->SetWidgetLoweredState(OSK_WIDGET_SHIFT, HasBit(_keystate, KEYS_SHIFT));
   119 
    84 			w->SetWidgetLoweredState(OSK_WIDGET_CAPS, HasBit(_keystate, KEYS_CAPS));
   120 			if (!IsValidChar(c, this->qs->afilter)) return;
    85 
   121 
    86 			ChangeOskDiabledState(w, qs, shift);
   122 			if (InsertTextBufferChar(&this->qs->text, c)) this->InvalidateWidget(OSK_WIDGET_TEXT);
    87 
   123 
    88 			SetDParam(0, qs->caption);
   124 			if (HasBit(_keystate, KEYS_SHIFT)) {
    89 			DrawWindowWidgets(w);
   125 				ToggleBit(_keystate, KEYS_SHIFT);
    90 
   126 				this->widget[OSK_WIDGET_SHIFT].color = HasBit(_keystate, KEYS_SHIFT) ? 15 : 14;
    91 			for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
   127 				this->SetDirty();
    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 			}
   128 			}
    97 
   129 			return;
    98 			DrawEditBox(w, qs, OSK_WIDGET_TEXT);
   130 		}
    99 			break;
   131 
   100 		}
   132 		switch (widget) {
   101 
   133 			case OSK_WIDGET_BACKSPACE:
   102 		case WE_CLICK:
   134 				if (DeleteTextBufferChar(&this->qs->text, WKC_BACKSPACE)) this->InvalidateWidget(OSK_WIDGET_TEXT);
   103 			/* clicked a letter */
   135 				break;
   104 			if (e->we.click.widget >= OSK_WIDGET_LETTERS) {
   136 
   105 				bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
   137 			case OSK_WIDGET_SPECIAL:
   106 
   138 				/*
   107 				WChar c = _keyboard[shift][e->we.click.widget - OSK_WIDGET_LETTERS];
   139 				 * Anything device specific can go here.
   108 
   140 				 * The button itself is hidden by default, and when you need it you
   109 				if (!IsValidChar(c, qs->afilter)) break;
   141 				 * can not hide it in the create event.
   110 
   142 				 */
   111 				if (InsertTextBufferChar(&qs->text, c)) w->InvalidateWidget(OSK_WIDGET_TEXT);
   143 				break;
   112 
   144 
   113 				if (HasBit(_keystate, KEYS_SHIFT)) {
   145 			case OSK_WIDGET_CAPS:
   114 					ToggleBit(_keystate, KEYS_SHIFT);
   146 				ToggleBit(_keystate, KEYS_CAPS);
   115 					w->widget[OSK_WIDGET_SHIFT].color = HasBit(_keystate, KEYS_SHIFT) ? 15 : 14;
   147 				this->SetDirty();
   116 					w->SetDirty();
   148 				break;
       
   149 
       
   150 			case OSK_WIDGET_SHIFT:
       
   151 				ToggleBit(_keystate, KEYS_SHIFT);
       
   152 				this->SetDirty();
       
   153 				break;
       
   154 
       
   155 			case OSK_WIDGET_SPACE:
       
   156 				if (InsertTextBufferChar(&this->qs->text, ' ')) this->InvalidateWidget(OSK_WIDGET_TEXT);
       
   157 				break;
       
   158 
       
   159 			case OSK_WIDGET_LEFT:
       
   160 				if (MoveTextBufferPos(&this->qs->text, WKC_LEFT)) this->InvalidateWidget(OSK_WIDGET_TEXT);
       
   161 				break;
       
   162 
       
   163 			case OSK_WIDGET_RIGHT:
       
   164 				if (MoveTextBufferPos(&this->qs->text, WKC_RIGHT)) this->InvalidateWidget(OSK_WIDGET_TEXT);
       
   165 				break;
       
   166 
       
   167 			case OSK_WIDGET_OK:
       
   168 				if (this->qs->orig == NULL || strcmp(this->qs->text.buf, this->qs->orig) != 0) {
       
   169 					/* pass information by simulating a button press on parent window */
       
   170 					if (this->ok_btn != 0) {
       
   171 						this->parent->OnClick(pt, this->ok_btn);
       
   172 					}
   117 				}
   173 				}
   118 				break;
   174 				delete this;
   119 			}
   175 				break;
   120 
   176 
   121 			switch (e->we.click.widget) {
   177 			case OSK_WIDGET_CANCEL:
   122 				case OSK_WIDGET_BACKSPACE:
   178 				if (this->cancel_btn != 0) { // pass a cancel event to the parent window
   123 					if (DeleteTextBufferChar(&qs->text, WKC_BACKSPACE)) w->InvalidateWidget(OSK_WIDGET_TEXT);
   179 					this->parent->OnClick(pt, this->cancel_btn);
   124 					break;
   180 					/* Window gets deleted when the parent window removes itself. */
   125 
   181 				} else { // or reset to original string
   126 				case OSK_WIDGET_SPECIAL:
   182 					strcpy(qs->text.buf, this->orig_str_buf);
   127 					/*
   183 					UpdateTextBufferSize(&qs->text);
   128 					 * Anything device specific can go here.
   184 					MoveTextBufferPos(&qs->text, WKC_END);
   129 					 * The button itself is hidden by default, and when you need it you
   185 					delete this;
   130 					 * can not hide it in the create event.
   186 				}
   131 					 */
   187 				break;
   132 					break;
   188 		}
   133 
   189 		/* make sure that the parent window's textbox also gets updated */
   134 				case OSK_WIDGET_CAPS:
   190 		if (this->parent != NULL) this->parent->InvalidateWidget(this->text_btn);
   135 					ToggleBit(_keystate, KEYS_CAPS);
   191 	}
   136 					w->SetDirty();
   192 
   137 					break;
   193 	virtual void OnMouseLoop()
   138 
   194 	{
   139 				case OSK_WIDGET_SHIFT:
   195 		this->qs->HandleEditBox(this, OSK_WIDGET_TEXT);
   140 					ToggleBit(_keystate, KEYS_SHIFT);
   196 		/* make the caret of the parent window also blink */
   141 					w->SetDirty();
   197 		this->parent->InvalidateWidget(this->text_btn);
   142 					break;
   198 	}
   143 
   199 };
   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 							w->parent->OnClick(e->we.click.pt, WP(w, osk_d).ok_btn);
       
   161 						}
       
   162 					}
       
   163 					delete w;
       
   164 					break;
       
   165 
       
   166 				case OSK_WIDGET_CANCEL:
       
   167 					if (WP(w, osk_d).cancel_btn != 0) { // pass a cancel event to the parent window
       
   168 						w->parent->OnClick(e->we.click.pt, WP(w, osk_d).cancel_btn);
       
   169 						/* Window gets deleted when the parent window removes itself. */
       
   170 					} else { // or reset to original string
       
   171 						strcpy(qs->text.buf, WP(w, osk_d).orig);
       
   172 						UpdateTextBufferSize(&qs->text);
       
   173 						MoveTextBufferPos(&qs->text, WKC_END);
       
   174 						delete w;
       
   175 					}
       
   176 					break;
       
   177 			}
       
   178 			/* make sure that the parent window's textbox also gets updated */
       
   179 			if (w->parent != NULL) w->parent->InvalidateWidget(WP(w, osk_d).text_btn);
       
   180 			break;
       
   181 
       
   182 		case WE_MOUSELOOP:
       
   183 			HandleEditBox(w, qs, OSK_WIDGET_TEXT);
       
   184 			/* make the caret of the parent window also blink */
       
   185 			w->parent->InvalidateWidget(WP(w, osk_d).text_btn);
       
   186 			break;
       
   187 	}
       
   188 }
       
   189 
   200 
   190 static const Widget _osk_widgets[] = {
   201 static const Widget _osk_widgets[] = {
   191 {      WWT_EMPTY, RESIZE_NONE,     0,     0,     0,     0,     0, 0x0,               STR_NULL},
   202 {      WWT_EMPTY, RESIZE_NONE,     0,     0,     0,     0,     0, 0x0,               STR_NULL},
   192 {    WWT_CAPTION, RESIZE_NONE,    14,     0,   255,     0,    13, STR_012D,          STR_NULL},
   203 {    WWT_CAPTION, RESIZE_NONE,    14,     0,   255,     0,    13, STR_012D,          STR_NULL},
   193 {      WWT_PANEL, RESIZE_NONE,    14,     0,   255,    14,    29, 0x0,               STR_NULL},
   204 {      WWT_PANEL, RESIZE_NONE,    14,     0,   255,    14,    29, 0x0,               STR_NULL},
   268 WindowDesc _osk_desc = {
   279 WindowDesc _osk_desc = {
   269 	WDP_CENTER, WDP_CENTER, 256, 140, 256, 140,
   280 	WDP_CENTER, WDP_CENTER, 256, 140, 256, 140,
   270 	WC_OSK, WC_NONE,
   281 	WC_OSK, WC_NONE,
   271 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   282 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   272 	_osk_widgets,
   283 	_osk_widgets,
   273 	OskWndProc
   284 	NULL
   274 };
   285 };
   275 
   286 
   276 /**
   287 /**
   277  * Retrieve keyboard layout from language string or (if set) config file.
   288  * Retrieve keyboard layout from language string or (if set) config file.
   278  * Also check for invalid characters.
   289  * Also check for invalid characters.
   336  * @param cancel widget number of parent's cancel button (0 if cancel events
   347  * @param cancel widget number of parent's cancel button (0 if cancel events
   337  *               should not be passed)
   348  *               should not be passed)
   338  * @param ok     widget number of parent's ok button  (0 if ok events should not
   349  * @param ok     widget number of parent's ok button  (0 if ok events should not
   339  *               be passed)
   350  *               be passed)
   340  */
   351  */
   341 void ShowOnScreenKeyboard(Window *parent, querystr_d *q, int button, int cancel, int ok)
   352 void ShowOnScreenKeyboard(QueryStringBaseWindow *parent, int button, int cancel, int ok)
   342 {
   353 {
   343 	DeleteWindowById(WC_OSK, 0);
   354 	DeleteWindowById(WC_OSK, 0);
   344 
   355 
   345 	Window *w = new Window(&_osk_desc);
       
   346 
       
   347 	w->parent = parent;
       
   348 	assert(parent != NULL);
       
   349 
       
   350 	if (parent->widget[button].data != 0) q->caption = parent->widget[button].data;
       
   351 
       
   352 	WP(w, osk_d).qs         = q;
       
   353 	WP(w, osk_d).text_btn   = button;
       
   354 	WP(w, osk_d).cancel_btn = cancel;
       
   355 	WP(w, osk_d).ok_btn     = ok;
       
   356 	WP(w, osk_d).text       = &q->text;
       
   357 
       
   358 	GetKeyboardLayout();
   356 	GetKeyboardLayout();
   359 
   357 	new OskWindow(&_osk_desc, parent, button, cancel, ok);
   360 	/* make a copy in case we need to reset later */
       
   361 	strcpy(_orig_str_buf, WP(w, osk_d).qs->text.buf);
       
   362 	WP(w, osk_d).orig = _orig_str_buf;
       
   363 }
   358 }