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 StringID caption; ///< the caption for this window. |
55 * only insert a space when we have a spacebar to do that or |
46 QueryString *qs; ///< text-input |
56 * characters that are not allowed to be entered. |
47 int text_btn; ///< widget number of parent's text field |
57 */ |
48 int ok_btn; ///< widget number of parent's ok button (=0 when ok shouldn't be passed on) |
58 static void ChangeOskDiabledState(Window *w, const querystr_d *qs, bool shift) |
49 int cancel_btn; ///< widget number of parent's cancel button (=0 when cancel shouldn't be passed on; text will be reverted to original) |
59 { |
50 Textbuf *text; ///< pointer to parent's textbuffer (to update caret position) |
60 for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) { |
51 char orig_str_buf[64]; ///< Original string. |
61 w->SetWidgetDisabledState(OSK_WIDGET_LETTERS + i, |
52 |
62 !IsValidChar(_keyboard[shift][i], qs->afilter) || _keyboard[shift][i] == ' '); |
53 OskWindow(const WindowDesc *desc, QueryStringBaseWindow *parent, int button, int cancel, int ok) : Window(desc) |
63 } |
54 { |
64 w->SetWidgetDisabledState(OSK_WIDGET_SPACE, !IsValidChar(' ', qs->afilter)); |
55 this->parent = parent; |
65 } |
56 assert(parent != NULL); |
66 |
57 |
67 /* on screen keyboard */ |
58 this->caption = (parent->widget[button].data != STR_NULL) ? parent->widget[button].data : parent->caption; |
68 static void OskWndProc(Window *w, WindowEvent *e) |
59 |
69 { |
60 this->qs = parent; |
70 querystr_d *qs = WP(w, osk_d).qs; |
61 this->text_btn = button; |
71 |
62 this->cancel_btn = cancel; |
72 switch (e->event) { |
63 this->ok_btn = ok; |
73 case WE_CREATE: |
64 this->text = &parent->text; |
74 SetBit(_no_scroll, SCROLL_EDIT); |
65 |
75 /* Not needed by default. */ |
66 /* make a copy in case we need to reset later */ |
76 w->DisableWidget(OSK_WIDGET_SPECIAL); |
67 strcpy(this->orig_str_buf, this->qs->text.buf); |
77 break; |
68 |
78 |
69 SetBit(_no_scroll, SCROLL_EDIT); |
79 case WE_PAINT: { |
70 /* Not needed by default. */ |
|
71 this->DisableWidget(OSK_WIDGET_SPECIAL); |
|
72 |
|
73 this->FindWindowPlacementAndResize(desc); |
|
74 } |
|
75 |
|
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. |
|
80 */ |
|
81 void ChangeOskDiabledState(bool shift) |
|
82 { |
|
83 for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) { |
|
84 this->SetWidgetDisabledState(OSK_WIDGET_LETTERS + i, |
|
85 !IsValidChar(_keyboard[shift][i], this->qs->afilter) || _keyboard[shift][i] == ' '); |
|
86 } |
|
87 this->SetWidgetDisabledState(OSK_WIDGET_SPACE, !IsValidChar(' ', this->qs->afilter)); |
|
88 } |
|
89 |
|
90 virtual void OnPaint() |
|
91 { |
|
92 bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT); |
|
93 |
|
94 this->LowerWidget(OSK_WIDGET_TEXT); |
|
95 this->SetWidgetLoweredState(OSK_WIDGET_SHIFT, HasBit(_keystate, KEYS_SHIFT)); |
|
96 this->SetWidgetLoweredState(OSK_WIDGET_CAPS, HasBit(_keystate, KEYS_CAPS)); |
|
97 |
|
98 this->ChangeOskDiabledState(shift); |
|
99 |
|
100 SetDParam(0, this->caption); |
|
101 this->DrawWidgets(); |
|
102 |
|
103 for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) { |
|
104 DrawCharCentered(_keyboard[shift][i], |
|
105 this->widget[OSK_WIDGET_LETTERS + i].left + 8, |
|
106 this->widget[OSK_WIDGET_LETTERS + i].top + 3, |
|
107 TC_BLACK); |
|
108 } |
|
109 |
|
110 this->qs->DrawEditBox(this, OSK_WIDGET_TEXT); |
|
111 } |
|
112 |
|
113 virtual void OnClick(Point pt, int widget) |
|
114 { |
|
115 /* clicked a letter */ |
|
116 if (widget >= OSK_WIDGET_LETTERS) { |
80 bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT); |
117 bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT); |
81 |
118 |
82 w->LowerWidget(OSK_WIDGET_TEXT); |
119 WChar c = _keyboard[shift][widget - OSK_WIDGET_LETTERS]; |
83 w->SetWidgetLoweredState(OSK_WIDGET_SHIFT, HasBit(_keystate, KEYS_SHIFT)); |
120 |
84 w->SetWidgetLoweredState(OSK_WIDGET_CAPS, HasBit(_keystate, KEYS_CAPS)); |
121 if (!IsValidChar(c, this->qs->afilter)) return; |
85 |
122 |
86 ChangeOskDiabledState(w, qs, shift); |
123 if (InsertTextBufferChar(&this->qs->text, c)) this->InvalidateWidget(OSK_WIDGET_TEXT); |
87 |
124 |
88 SetDParam(0, qs->caption); |
125 if (HasBit(_keystate, KEYS_SHIFT)) { |
89 DrawWindowWidgets(w); |
126 ToggleBit(_keystate, KEYS_SHIFT); |
90 |
127 this->widget[OSK_WIDGET_SHIFT].color = HasBit(_keystate, KEYS_SHIFT) ? 15 : 14; |
91 for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) { |
128 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 } |
129 } |
97 |
130 return; |
98 DrawEditBox(w, qs, OSK_WIDGET_TEXT); |
131 } |
99 break; |
132 |
100 } |
133 bool delete_this = false; |
101 |
134 |
102 case WE_CLICK: |
135 switch (widget) { |
103 /* clicked a letter */ |
136 case OSK_WIDGET_BACKSPACE: |
104 if (e->we.click.widget >= OSK_WIDGET_LETTERS) { |
137 if (DeleteTextBufferChar(&this->qs->text, WKC_BACKSPACE)) this->InvalidateWidget(OSK_WIDGET_TEXT); |
105 bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT); |
138 break; |
106 |
139 |
107 WChar c = _keyboard[shift][e->we.click.widget - OSK_WIDGET_LETTERS]; |
140 case OSK_WIDGET_SPECIAL: |
108 |
141 /* |
109 if (!IsValidChar(c, qs->afilter)) break; |
142 * Anything device specific can go here. |
110 |
143 * The button itself is hidden by default, and when you need it you |
111 if (InsertTextBufferChar(&qs->text, c)) w->InvalidateWidget(OSK_WIDGET_TEXT); |
144 * can not hide it in the create event. |
112 |
145 */ |
113 if (HasBit(_keystate, KEYS_SHIFT)) { |
146 break; |
114 ToggleBit(_keystate, KEYS_SHIFT); |
147 |
115 w->widget[OSK_WIDGET_SHIFT].color = HasBit(_keystate, KEYS_SHIFT) ? 15 : 14; |
148 case OSK_WIDGET_CAPS: |
116 SetWindowDirty(w); |
149 ToggleBit(_keystate, KEYS_CAPS); |
|
150 this->SetDirty(); |
|
151 break; |
|
152 |
|
153 case OSK_WIDGET_SHIFT: |
|
154 ToggleBit(_keystate, KEYS_SHIFT); |
|
155 this->SetDirty(); |
|
156 break; |
|
157 |
|
158 case OSK_WIDGET_SPACE: |
|
159 if (InsertTextBufferChar(&this->qs->text, ' ')) this->InvalidateWidget(OSK_WIDGET_TEXT); |
|
160 break; |
|
161 |
|
162 case OSK_WIDGET_LEFT: |
|
163 if (MoveTextBufferPos(&this->qs->text, WKC_LEFT)) this->InvalidateWidget(OSK_WIDGET_TEXT); |
|
164 break; |
|
165 |
|
166 case OSK_WIDGET_RIGHT: |
|
167 if (MoveTextBufferPos(&this->qs->text, WKC_RIGHT)) this->InvalidateWidget(OSK_WIDGET_TEXT); |
|
168 break; |
|
169 |
|
170 case OSK_WIDGET_OK: |
|
171 if (this->qs->orig == NULL || strcmp(this->qs->text.buf, this->qs->orig) != 0) { |
|
172 /* pass information by simulating a button press on parent window */ |
|
173 if (this->ok_btn != 0) { |
|
174 this->parent->OnClick(pt, this->ok_btn); |
|
175 /* Window gets deleted when the parent window removes itself. */ |
|
176 return; |
|
177 } |
117 } |
178 } |
118 break; |
179 delete_this = true; |
119 } |
180 break; |
120 |
181 |
121 switch (e->we.click.widget) { |
182 case OSK_WIDGET_CANCEL: |
122 case OSK_WIDGET_BACKSPACE: |
183 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); |
184 this->parent->OnClick(pt, this->cancel_btn); |
124 break; |
185 /* Window gets deleted when the parent window removes itself. */ |
125 |
186 return; |
126 case OSK_WIDGET_SPECIAL: |
187 } else { // or reset to original string |
127 /* |
188 strcpy(qs->text.buf, this->orig_str_buf); |
128 * Anything device specific can go here. |
189 UpdateTextBufferSize(&qs->text); |
129 * The button itself is hidden by default, and when you need it you |
190 MoveTextBufferPos(&qs->text, WKC_END); |
130 * can not hide it in the create event. |
191 delete_this = true; |
131 */ |
192 } |
132 break; |
193 break; |
133 |
194 } |
134 case OSK_WIDGET_CAPS: |
195 /* make sure that the parent window's textbox also gets updated */ |
135 ToggleBit(_keystate, KEYS_CAPS); |
196 if (this->parent != NULL) this->parent->InvalidateWidget(this->text_btn); |
136 SetWindowDirty(w); |
197 if (delete_this) delete this; |
137 break; |
198 } |
138 |
199 |
139 case OSK_WIDGET_SHIFT: |
200 virtual void OnMouseLoop() |
140 ToggleBit(_keystate, KEYS_SHIFT); |
201 { |
141 SetWindowDirty(w); |
202 this->qs->HandleEditBox(this, OSK_WIDGET_TEXT); |
142 break; |
203 /* make the caret of the parent window also blink */ |
143 |
204 this->parent->InvalidateWidget(this->text_btn); |
144 case OSK_WIDGET_SPACE: |
205 } |
145 if (InsertTextBufferChar(&qs->text, ' ')) w->InvalidateWidget(OSK_WIDGET_TEXT); |
206 }; |
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 |
207 |
197 static const Widget _osk_widgets[] = { |
208 static const Widget _osk_widgets[] = { |
198 { WWT_EMPTY, RESIZE_NONE, 0, 0, 0, 0, 0, 0x0, STR_NULL}, |
209 { 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}, |
210 { 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}, |
211 { WWT_PANEL, RESIZE_NONE, 14, 0, 255, 14, 29, 0x0, STR_NULL}, |