# osk_gui.cpp

Go to the documentation of this file.

    00001 /* $Id: osk_gui.cpp 15723 2009-03-15 15:12:06Z rubidium $ */
    00002
    00005 #include "stdafx.h"
    00006 #include "string_func.h"
    00007 #include "strings_func.h"
    00008 #include "debug.h"
    00009 #include "window_func.h"
    00010 #include "gfx_func.h"
    00011 #include "querystring_gui.h"
    00012
    00013 #include "table/sprites.h"
    00014 #include "table/strings.h"
    00015
    00016 enum OskWidgets {
    00017   OSK_WIDGET_TEXT = 3,
    00018   OSK_WIDGET_CANCEL = 5,
    00019   OSK_WIDGET_OK,
    00020   OSK_WIDGET_BACKSPACE,
    00021   OSK_WIDGET_SPECIAL,
    00022   OSK_WIDGET_CAPS,
    00023   OSK_WIDGET_SHIFT,
    00024   OSK_WIDGET_SPACE,
    00025   OSK_WIDGET_LEFT,
    00026   OSK_WIDGET_RIGHT,
    00027   OSK_WIDGET_LETTERS
    00028 };
    00029
    00030 char _keyboard_opt[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
    00031 static WChar _keyboard[2][OSK_KEYBOARD_ENTRIES];
    00032
    00033 enum {
    00034   KEYS_NONE,
    00035   KEYS_SHIFT,
    00036   KEYS_CAPS
    00037 };
    00038 static byte _keystate = KEYS_NONE;
    00039
    00040 struct OskWindow : public Window {
    00041   StringID caption;
    00042   QueryString *qs;
    00043   int text_btn;
    00044   int ok_btn;
    00045   int cancel_btn;
    00046   Textbuf *text;
    00047   char *orig_str_buf;
    00048
    00049   OskWindow(const WindowDesc *desc, QueryStringBaseWindow *parent, int button, int cancel, int ok) : Window(desc)
    00050   {
    00051     this->parent = parent;
    00052     assert(parent != NULL);
    00053
    00054     this->caption = (parent->widget[button].data != STR_NULL) ? parent->widget[button].data : parent->caption;
    00055
    00056     this->qs         = parent;
    00057     this->text_btn   = button;
    00058     this->cancel_btn = cancel;
    00059     this->ok_btn     = ok;
    00060     this->text       = &parent->text;
    00061
    00062     /* make a copy in case we need to reset later */
    00063     this->orig_str_buf = strdup(this->qs->text.buf);
    00064
    00065     /* Not needed by default. */
    00066     this->DisableWidget(OSK_WIDGET_SPECIAL);
    00067
    00068     this->FindWindowPlacementAndResize(desc);
    00069   }
    00070
    00071   ~OskWindow()
    00072   {
    00073     free(this->orig_str_buf);
    00074   }
    00075
    00081   void ChangeOskDiabledState(bool shift)
    00082   {
    00083     for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
    00084       this->SetWidgetDisabledState(OSK_WIDGET_LETTERS + i,
    00085           !IsValidChar(_keyboard[shift][i], this->qs->afilter) || _keyboard[shift][i] == ' ');
    00086     }
    00087     this->SetWidgetDisabledState(OSK_WIDGET_SPACE, !IsValidChar(' ', this->qs->afilter));
    00088   }
    00089
    00090   virtual void OnPaint()
    00091   {
    00092     bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
    00093
    00094     this->LowerWidget(OSK_WIDGET_TEXT);
    00095     this->SetWidgetLoweredState(OSK_WIDGET_SHIFT, HasBit(_keystate, KEYS_SHIFT));
    00096     this->SetWidgetLoweredState(OSK_WIDGET_CAPS, HasBit(_keystate, KEYS_CAPS));
    00097
    00098     this->ChangeOskDiabledState(shift);
    00099
    00100     SetDParam(0, this->caption);
    00101     this->DrawWidgets();
    00102
    00103     for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
    00104       DrawCharCentered(_keyboard[shift][i],
    00105         this->widget[OSK_WIDGET_LETTERS + i].left + 8,
    00106         this->widget[OSK_WIDGET_LETTERS + i].top + 3,
    00107         TC_BLACK);
    00108     }
    00109
    00110     this->qs->DrawEditBox(this, OSK_WIDGET_TEXT);
    00111   }
    00112
    00113   virtual void OnClick(Point pt, int widget)
    00114   {
    00115     /* clicked a letter */
    00116     if (widget >= OSK_WIDGET_LETTERS) {
    00117       bool shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
    00118
    00119       WChar c = _keyboard[shift][widget - OSK_WIDGET_LETTERS];
    00120
    00121       if (!IsValidChar(c, this->qs->afilter)) return;
    00122
    00123       if (InsertTextBufferChar(&this->qs->text, c)) this->InvalidateParent();
    00124
    00125       if (HasBit(_keystate, KEYS_SHIFT)) {
    00126         ToggleBit(_keystate, KEYS_SHIFT);
    00127         this->widget[OSK_WIDGET_SHIFT].colour = HasBit(_keystate, KEYS_SHIFT) ? COLOUR_WHITE : COLOUR_GREY;
    00128         this->SetDirty();
    00129       }
    00130       return;
    00131     }
    00132
    00133     switch (widget) {
    00134       case OSK_WIDGET_TEXT:
    00135         /* Find the edit box of the parent window and give focus to that */
    00136         for (uint i = 0; i < this->parent->widget_count; i++) {
    00137           Widget &wi = this->parent->widget[i];
    00138           if (wi.type == WWT_EDITBOX) {
    00139             this->parent->focused_widget = &wi;
    00140             break;
    00141           }
    00142         }
    00143
    00144         /* Give focus to parent window */
    00145         SetFocusedWindow(this->parent);
    00146
    00147         break;
    00148
    00149       case OSK_WIDGET_BACKSPACE:
    00150         if (DeleteTextBufferChar(&this->qs->text, WKC_BACKSPACE)) this->InvalidateParent();
    00151         break;
    00152
    00153       case OSK_WIDGET_SPECIAL:
    00154         /*
    00155          * Anything device specific can go here.
    00156          * The button itself is hidden by default, and when you need it you
    00157          * can not hide it in the create event.
    00158          */
    00159         break;
    00160
    00161       case OSK_WIDGET_CAPS:
    00162         ToggleBit(_keystate, KEYS_CAPS);
    00163         this->SetDirty();
    00164         break;
    00165
    00166       case OSK_WIDGET_SHIFT:
    00167         ToggleBit(_keystate, KEYS_SHIFT);
    00168         this->SetDirty();
    00169         break;
    00170
    00171       case OSK_WIDGET_SPACE:
    00172         if (InsertTextBufferChar(&this->qs->text, ' ')) this->InvalidateParent();
    00173         break;
    00174
    00175       case OSK_WIDGET_LEFT:
    00176         if (MoveTextBufferPos(&this->qs->text, WKC_LEFT)) this->InvalidateParent();
    00177         break;
    00178
    00179       case OSK_WIDGET_RIGHT:
    00180         if (MoveTextBufferPos(&this->qs->text, WKC_RIGHT)) this->InvalidateParent();
    00181         break;
    00182
    00183       case OSK_WIDGET_OK:
    00184         if (this->qs->orig == NULL || strcmp(this->qs->text.buf, this->qs->orig) != 0) {
    00185           /* pass information by simulating a button press on parent window */
    00186           if (this->ok_btn != 0) {
    00187             this->parent->OnClick(pt, this->ok_btn);
    00188             /* Window gets deleted when the parent window removes itself. */
    00189             return;
    00190           }
    00191         }
    00192         delete this;
    00193         break;
    00194
    00195       case OSK_WIDGET_CANCEL:
    00196         if (this->cancel_btn != 0) { // pass a cancel event to the parent window
    00197           this->parent->OnClick(pt, this->cancel_btn);
    00198           /* Window gets deleted when the parent window removes itself. */
    00199           return;
    00200         } else { // or reset to original string
    00201           strcpy(qs->text.buf, this->orig_str_buf);
    00202           UpdateTextBufferSize(&qs->text);
    00203           MoveTextBufferPos(&qs->text, WKC_END);
    00204           this->InvalidateParent();
    00205           delete this;
    00206         }
    00207         break;
    00208     }
    00209   }
    00210
    00211   void InvalidateParent()
    00212   {
    00213     QueryStringBaseWindow *w = dynamic_cast<QueryStringBaseWindow*>(this->parent);
    00214     if (w != NULL) w->OnOSKInput(this->text_btn);
    00215
    00216     this->InvalidateWidget(OSK_WIDGET_TEXT);
    00217     if (this->parent != NULL) this->parent->InvalidateWidget(this->text_btn);
    00218   }
    00219
    00220   virtual void OnMouseLoop()
    00221   {
    00222     this->qs->HandleEditBox(this, OSK_WIDGET_TEXT);
    00223     /* make the caret of the parent window also blink */
    00224     this->parent->InvalidateWidget(this->text_btn);
    00225   }
    00226
    00227   virtual void OnInvalidateData(int)
    00228   {
    00229     this->InvalidateWidget(OSK_WIDGET_TEXT);
    00230   }
    00231 };
    00232
    00233 static const Widget _osk_widgets[] = {
    00234 {      WWT_EMPTY, RESIZE_NONE,  COLOUR_GREY,     0,     0,     0,     0, 0x0,               STR_NULL},
    00235 {    WWT_CAPTION, RESIZE_NONE,  COLOUR_GREY,     0,   255,     0,    13, STR_012D,          STR_NULL},
    00236 {      WWT_PANEL, RESIZE_NONE,  COLOUR_GREY,     0,   255,    14,    29, 0x0,               STR_NULL},
    00237 {    WWT_EDITBOX, RESIZE_NONE,  COLOUR_GREY,     2,   253,    16,    27, 0x0,               STR_NULL},
    00238
    00239 {      WWT_PANEL, RESIZE_NONE,  COLOUR_GREY,     0,   255,    30,   139, 0x0,               STR_NULL},
    00240
    00241 {    WWT_TEXTBTN, RESIZE_NONE,  COLOUR_GREY,     3,   108,    35,    46, STR_012E_CANCEL,   STR_NULL},
    00242 {    WWT_TEXTBTN, RESIZE_NONE,  COLOUR_GREY,   111,   216,    35,    46, STR_012F_OK,       STR_NULL},
    00243 { WWT_PUSHIMGBTN, RESIZE_NONE,  COLOUR_GREY,   219,   252,    35,    46, SPR_OSK_BACKSPACE, STR_NULL},
    00244
    00245 { WWT_PUSHIMGBTN, RESIZE_NONE,  COLOUR_GREY,     3,    27,    67,    82, SPR_OSK_SPECIAL,   STR_NULL},
    00246 {     WWT_IMGBTN, RESIZE_NONE,  COLOUR_GREY,     3,    36,    85,   100, SPR_OSK_CAPS,      STR_NULL},
    00247 {     WWT_IMGBTN, RESIZE_NONE,  COLOUR_GREY,     3,    27,   103,   118, SPR_OSK_SHIFT,     STR_NULL},
    00248
    00249 { WWT_PUSHTXTBTN, RESIZE_NONE,  COLOUR_GREY,    75,   189,   121,   136, STR_EMPTY,         STR_NULL},
    00250
    00251 { WWT_PUSHIMGBTN, RESIZE_NONE,  COLOUR_GREY,   219,   234,   121,   136, SPR_OSK_LEFT,      STR_NULL},
    00252 { WWT_PUSHIMGBTN, RESIZE_NONE,  COLOUR_GREY,   237,   252,   121,   136, SPR_OSK_RIGHT,     STR_NULL},
    00253
    00254 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,     3,    18,    49,    64, 0x0,    STR_NULL},
    00255 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    21,    36,    49,    64, 0x0,    STR_NULL},
    00256 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    39,    54,    49,    64, 0x0,    STR_NULL},
    00257 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    57,    72,    49,    64, 0x0,    STR_NULL},
    00258 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    75,    90,    49,    64, 0x0,    STR_NULL},
    00259 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    93,   108,    49,    64, 0x0,    STR_NULL},
    00260 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   111,   126,    49,    64, 0x0,    STR_NULL},
    00261 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   129,   144,    49,    64, 0x0,    STR_NULL},
    00262 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   147,   162,    49,    64, 0x0,    STR_NULL},
    00263 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   165,   180,    49,    64, 0x0,    STR_NULL},
    00264 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   183,   198,    49,    64, 0x0,    STR_NULL},
    00265 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   201,   216,    49,    64, 0x0,    STR_NULL},
    00266 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   219,   234,    49,    64, 0x0,    STR_NULL},
    00267 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   237,   252,    49,    64, 0x0,    STR_NULL},
    00268
    00269 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    30,    45,    67,    82, 0x0,    STR_NULL},
    00270 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    48,    63,    67,    82, 0x0,    STR_NULL},
    00271 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    66,    81,    67,    82, 0x0,    STR_NULL},
    00272 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    84,    99,    67,    82, 0x0,    STR_NULL},
    00273 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   102,   117,    67,    82, 0x0,    STR_NULL},
    00274 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   120,   135,    67,    82, 0x0,    STR_NULL},
    00275 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   138,   153,    67,    82, 0x0,    STR_NULL},
    00276 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   156,   171,    67,    82, 0x0,    STR_NULL},
    00277 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   174,   189,    67,    82, 0x0,    STR_NULL},
    00278 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   192,   207,    67,    82, 0x0,    STR_NULL},
    00279 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   210,   225,    67,    82, 0x0,    STR_NULL},
    00280 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   228,   243,    67,    82, 0x0,    STR_NULL},
    00281
    00282 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    39,    54,    85,   100, 0x0,    STR_NULL},
    00283 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    57,    72,    85,   100, 0x0,    STR_NULL},
    00284 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    75,    90,    85,   100, 0x0,    STR_NULL},
    00285 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    93,   108,    85,   100, 0x0,    STR_NULL},
    00286 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   111,   126,    85,   100, 0x0,    STR_NULL},
    00287 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   129,   144,    85,   100, 0x0,    STR_NULL},
    00288 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   147,   162,    85,   100, 0x0,    STR_NULL},
    00289 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   165,   180,    85,   100, 0x0,    STR_NULL},
    00290 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   183,   198,    85,   100, 0x0,    STR_NULL},
    00291 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   201,   216,    85,   100, 0x0,    STR_NULL},
    00292 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   219,   234,    85,   100, 0x0,    STR_NULL},
    00293 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   237,   252,    85,   100, 0x0,    STR_NULL},
    00294
    00295 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    30,    45,   103,   118, 0x0,    STR_NULL},
    00296 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    48,    63,   103,   118, 0x0,    STR_NULL},
    00297 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    66,    81,   103,   118, 0x0,    STR_NULL},
    00298 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,    84,    99,   103,   118, 0x0,    STR_NULL},
    00299 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   102,   117,   103,   118, 0x0,    STR_NULL},
    00300 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   120,   135,   103,   118, 0x0,    STR_NULL},
    00301 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   138,   153,   103,   118, 0x0,    STR_NULL},
    00302 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   156,   171,   103,   118, 0x0,    STR_NULL},
    00303 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   174,   189,   103,   118, 0x0,    STR_NULL},
    00304 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   192,   207,   103,   118, 0x0,    STR_NULL},
    00305 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   210,   225,   103,   118, 0x0,    STR_NULL},
    00306 {    WWT_PUSHBTN, RESIZE_NONE,  COLOUR_GREY,   228,   243,   103,   118, 0x0,    STR_NULL},
    00307
    00308 {   WIDGETS_END},
    00309 };
    00310
    00311 static const WindowDesc _osk_desc(
    00312   WDP_CENTER, WDP_CENTER, 256, 140, 256, 140,
    00313   WC_OSK, WC_NONE,
    00314   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
    00315   _osk_widgets
    00316 );
    00317
    00322 void GetKeyboardLayout()
    00323 {
    00324   char keyboard[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
    00325   char errormark[2][OSK_KEYBOARD_ENTRIES + 1]; // used for marking invalid chars
    00326   bool has_error = false; // true when an invalid char is detected
    00327
    00328   if (StrEmpty(_keyboard_opt[0])) {
    00329     GetString(keyboard[0], STR_OSK_KEYBOARD_LAYOUT, lastof(keyboard[0]));
    00330   } else {
    00331     strecpy(keyboard[0], _keyboard_opt[0], lastof(keyboard[0]));
    00332   }
    00333
    00334   if (StrEmpty(_keyboard_opt[1])) {
    00335     GetString(keyboard[1], STR_OSK_KEYBOARD_LAYOUT_CAPS, lastof(keyboard[1]));
    00336   } else {
    00337     strecpy(keyboard[1], _keyboard_opt[1], lastof(keyboard[1]));
    00338   }
    00339
    00340   for (uint j = 0; j < 2; j++) {
    00341     const char *kbd = keyboard[j];
    00342     bool ended = false;
    00343     for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
    00344       _keyboard[j][i] = Utf8Consume(&kbd);
    00345
    00346       /* Be lenient when the last characters are missing (is quite normal) */
    00347       if (_keyboard[j][i] == '\0' || ended) {
    00348         ended = true;
    00349         _keyboard[j][i] = ' ';
    00350         continue;
    00351       }
    00352
    00353       if (IsPrintable(_keyboard[j][i])) {
    00354         errormark[j][i] = ' ';
    00355       } else {
    00356         has_error = true;
    00357         errormark[j][i] = '^';
    00358         _keyboard[j][i] = ' ';
    00359       }
    00360     }
    00361   }
    00362
    00363   if (has_error) {
    00364     ShowInfoF("The keyboard layout you selected contains invalid chars. Please check those chars marked with ^.");
    00365     ShowInfoF("Normal keyboard:  %s", keyboard[0]);
    00366     ShowInfoF("                  %s", errormark[0]);
    00367     ShowInfoF("Caps Lock:        %s", keyboard[1]);
    00368     ShowInfoF("                  %s", errormark[1]);
    00369   }
    00370 }
    00371
    00383 void ShowOnScreenKeyboard(QueryStringBaseWindow *parent, int button, int cancel, int ok)
    00384 {
    00385   DeleteWindowById(WC_OSK, 0);
    00386
    00387   GetKeyboardLayout();
    00388   new OskWindow(&_osk_desc, parent, button, cancel, ok);
    00389 }