# osk_gui.cpp

Go to the documentation of this file.

    00001 /* $Id$ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * 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.
    00006  * 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.
    00007  * 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/>.
    00008  */
    00009
    00012 #include "stdafx.h"
    00013 #include "string_func.h"
    00014 #include "strings_func.h"
    00015 #include "debug.h"
    00016 #include "window_func.h"
    00017 #include "gfx_func.h"
    00018 #include "querystring_gui.h"
    00019
    00020 #include "widgets/osk_widget.h"
    00021
    00022 #include "table/sprites.h"
    00023 #include "table/strings.h"
    00024
    00025 char _keyboard_opt[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
    00026 static WChar _keyboard[2][OSK_KEYBOARD_ENTRIES];
    00027
    00028 enum KeyStateBits {
    00029   KEYS_NONE,
    00030   KEYS_SHIFT,
    00031   KEYS_CAPS
    00032 };
    00033 static byte _keystate = KEYS_NONE;
    00034
    00035 struct OskWindow : public Window {
    00036   StringID caption;
    00037   QueryString *qs;
    00038   int text_btn;
    00039   int ok_btn;
    00040   int cancel_btn;
    00041   Textbuf *text;
    00042   char *orig_str_buf;
    00043   bool shift;
    00044
    00045   OskWindow(const WindowDesc *desc, QueryStringBaseWindow *parent, int button, int cancel, int ok) : Window()
    00046   {
    00047     this->parent = parent;
    00048     assert(parent != NULL);
    00049
    00050     NWidgetCore *par_wid = parent->GetWidget<NWidgetCore>(button);
    00051     assert(par_wid != NULL);
    00052     this->caption = (par_wid->widget_data != STR_NULL) ? par_wid->widget_data : parent->caption;
    00053
    00054     this->qs         = parent;
    00055     this->text_btn   = button;
    00056     this->cancel_btn = cancel;
    00057     this->ok_btn     = ok;
    00058     this->text       = &parent->text;
    00059
    00060     /* make a copy in case we need to reset later */
    00061     this->orig_str_buf = strdup(this->qs->text.buf);
    00062
    00063     this->InitNested(desc, 0);
    00064
    00065     /* Not needed by default. */
    00066     this->DisableWidget(WID_OSK_SPECIAL);
    00067
    00068     this->UpdateOskState();
    00069   }
    00070
    00071   ~OskWindow()
    00072   {
    00073     free(this->orig_str_buf);
    00074   }
    00075
    00081   void UpdateOskState()
    00082   {
    00083     this->shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
    00084
    00085     for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
    00086       this->SetWidgetDisabledState(WID_OSK_LETTERS + i,
    00087           !IsValidChar(_keyboard[this->shift][i], this->qs->afilter) || _keyboard[this->shift][i] == ' ');
    00088     }
    00089     this->SetWidgetDisabledState(WID_OSK_SPACE, !IsValidChar(' ', this->qs->afilter));
    00090
    00091     this->LowerWidget(WID_OSK_TEXT);
    00092     this->SetWidgetLoweredState(WID_OSK_SHIFT, HasBit(_keystate, KEYS_SHIFT));
    00093     this->SetWidgetLoweredState(WID_OSK_CAPS, HasBit(_keystate, KEYS_CAPS));
    00094   }
    00095
    00096   virtual void SetStringParameters(int widget) const
    00097   {
    00098     if (widget == WID_OSK_CAPTION) SetDParam(0, this->caption);
    00099   }
    00100
    00101   virtual void DrawWidget(const Rect &r, int widget) const
    00102   {
    00103     if (widget < WID_OSK_LETTERS) return;
    00104
    00105     widget -= WID_OSK_LETTERS;
    00106     DrawCharCentered(_keyboard[this->shift][widget],
    00107       r.left + 8,
    00108       r.top + 3,
    00109       TC_BLACK);
    00110   }
    00111
    00112   virtual void OnPaint()
    00113   {
    00114     this->DrawWidgets();
    00115
    00116     this->qs->DrawEditBox(this, WID_OSK_TEXT);
    00117   }
    00118
    00119   virtual void OnClick(Point pt, int widget, int click_count)
    00120   {
    00121     /* clicked a letter */
    00122     if (widget >= WID_OSK_LETTERS) {
    00123       WChar c = _keyboard[this->shift][widget - WID_OSK_LETTERS];
    00124
    00125       if (!IsValidChar(c, this->qs->afilter)) return;
    00126
    00127       if (InsertTextBufferChar(&this->qs->text, c)) this->InvalidateParent();
    00128
    00129       if (HasBit(_keystate, KEYS_SHIFT)) {
    00130         ToggleBit(_keystate, KEYS_SHIFT);
    00131         this->GetWidget<NWidgetCore>(WID_OSK_SHIFT)->colour = HasBit(_keystate, KEYS_SHIFT) ? COLOUR_WHITE : COLOUR_GREY;
    00132         this->SetDirty();
    00133       }
    00134       /* Return focus to the parent widget and window. */
    00135       this->parent->SetFocusedWidget(this->text_btn);
    00136       SetFocusedWindow(this->parent);
    00137       return;
    00138     }
    00139
    00140     switch (widget) {
    00141       case WID_OSK_BACKSPACE:
    00142         if (DeleteTextBufferChar(&this->qs->text, WKC_BACKSPACE)) this->InvalidateParent();
    00143         break;
    00144
    00145       case WID_OSK_SPECIAL:
    00146         /*
    00147          * Anything device specific can go here.
    00148          * The button itself is hidden by default, and when you need it you
    00149          * can not hide it in the create event.
    00150          */
    00151         break;
    00152
    00153       case WID_OSK_CAPS:
    00154         ToggleBit(_keystate, KEYS_CAPS);
    00155         this->UpdateOskState();
    00156         this->SetDirty();
    00157         break;
    00158
    00159       case WID_OSK_SHIFT:
    00160         ToggleBit(_keystate, KEYS_SHIFT);
    00161         this->UpdateOskState();
    00162         this->SetDirty();
    00163         break;
    00164
    00165       case WID_OSK_SPACE:
    00166         if (InsertTextBufferChar(&this->qs->text, ' ')) this->InvalidateParent();
    00167         break;
    00168
    00169       case WID_OSK_LEFT:
    00170         if (MoveTextBufferPos(&this->qs->text, WKC_LEFT)) this->InvalidateParent();
    00171         break;
    00172
    00173       case WID_OSK_RIGHT:
    00174         if (MoveTextBufferPos(&this->qs->text, WKC_RIGHT)) this->InvalidateParent();
    00175         break;
    00176
    00177       case WID_OSK_OK:
    00178         if (this->qs->orig == NULL || strcmp(this->qs->text.buf, this->qs->orig) != 0) {
    00179           /* pass information by simulating a button press on parent window */
    00180           if (this->ok_btn != 0) {
    00181             this->parent->OnClick(pt, this->ok_btn, 1);
    00182             /* Window gets deleted when the parent window removes itself. */
    00183             return;
    00184           }
    00185         }
    00186         delete this;
    00187         break;
    00188
    00189       case WID_OSK_CANCEL:
    00190         if (this->cancel_btn != 0) { // pass a cancel event to the parent window
    00191           this->parent->OnClick(pt, this->cancel_btn, 1);
    00192           /* Window gets deleted when the parent window removes itself. */
    00193           return;
    00194         } else { // or reset to original string
    00195           strcpy(qs->text.buf, this->orig_str_buf);
    00196           UpdateTextBufferSize(&qs->text);
    00197           MoveTextBufferPos(&qs->text, WKC_END);
    00198           this->InvalidateParent();
    00199           delete this;
    00200         }
    00201         break;
    00202     }
    00203     /* Return focus to the parent widget and window. */
    00204     this->parent->SetFocusedWidget(this->text_btn);
    00205     SetFocusedWindow(this->parent);
    00206   }
    00207
    00208   void InvalidateParent()
    00209   {
    00210     QueryStringBaseWindow *w = dynamic_cast<QueryStringBaseWindow*>(this->parent);
    00211     if (w != NULL) w->OnOSKInput(this->text_btn);
    00212
    00213     this->SetWidgetDirty(WID_OSK_TEXT);
    00214     if (this->parent != NULL) this->parent->SetWidgetDirty(this->text_btn);
    00215   }
    00216
    00217   virtual void OnMouseLoop()
    00218   {
    00219     this->qs->HandleEditBox(this, WID_OSK_TEXT);
    00220     /* make the caret of the parent window also blink */
    00221     this->parent->SetWidgetDirty(this->text_btn);
    00222   }
    00223
    00229   virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
    00230   {
    00231     if (!gui_scope) return;
    00232     this->SetWidgetDirty(WID_OSK_TEXT);
    00233   }
    00234 };
    00235
    00236 static const int HALF_KEY_WIDTH = 7;  // Width of 1/2 key in pixels.
    00237 static const int INTER_KEY_SPACE = 2; // Number of pixels between two keys.
    00238
    00250 static void AddKey(NWidgetHorizontal *hor, int height, int num_half, WidgetType widtype, int widnum, uint16 widdata, int *biggest_index)
    00251 {
    00252   int key_width = HALF_KEY_WIDTH + (INTER_KEY_SPACE + HALF_KEY_WIDTH) * (num_half - 1);
    00253
    00254   if (widtype == NWID_SPACER) {
    00255     if (!hor->IsEmpty()) key_width += INTER_KEY_SPACE;
    00256     NWidgetSpacer *spc = new NWidgetSpacer(key_width, height);
    00257     hor->Add(spc);
    00258   } else {
    00259     if (!hor->IsEmpty()) {
    00260       NWidgetSpacer *spc = new NWidgetSpacer(INTER_KEY_SPACE, height);
    00261       hor->Add(spc);
    00262     }
    00263     NWidgetLeaf *leaf = new NWidgetLeaf(widtype, COLOUR_GREY, widnum, widdata, STR_NULL);
    00264     leaf->SetMinimalSize(key_width, height);
    00265     hor->Add(leaf);
    00266   }
    00267
    00268   *biggest_index = max(*biggest_index, widnum);
    00269 }
    00270
    00272 static NWidgetBase *MakeTopKeys(int *biggest_index)
    00273 {
    00274   NWidgetHorizontal *hor = new NWidgetHorizontal();
    00275   int key_height = FONT_HEIGHT_NORMAL + 2;
    00276
    00277   AddKey(hor, key_height, 6 * 2, WWT_TEXTBTN,    WID_OSK_CANCEL,    STR_BUTTON_CANCEL,  biggest_index);
    00278   AddKey(hor, key_height, 6 * 2, WWT_TEXTBTN,    WID_OSK_OK,        STR_BUTTON_OK,      biggest_index);
    00279   AddKey(hor, key_height, 2 * 2, WWT_PUSHIMGBTN, WID_OSK_BACKSPACE, SPR_OSK_BACKSPACE, biggest_index);
    00280   return hor;
    00281 }
    00282
    00284 static NWidgetBase *MakeNumberKeys(int *biggest_index)
    00285 {
    00286   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00287   int key_height = FONT_HEIGHT_NORMAL + 6;
    00288
    00289   for (int widnum = WID_OSK_NUMBERS_FIRST; widnum <= WID_OSK_NUMBERS_LAST; widnum++) {
    00290     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00291   }
    00292   return hor;
    00293 }
    00294
    00296 static NWidgetBase *MakeQwertyKeys(int *biggest_index)
    00297 {
    00298   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00299   int key_height = FONT_HEIGHT_NORMAL + 6;
    00300
    00301   AddKey(hor, key_height, 3, WWT_PUSHIMGBTN, WID_OSK_SPECIAL, SPR_OSK_SPECIAL, biggest_index);
    00302   for (int widnum = WID_OSK_QWERTY_FIRST; widnum <= WID_OSK_QWERTY_LAST; widnum++) {
    00303     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00304   }
    00305   AddKey(hor, key_height, 1, NWID_SPACER, 0, 0, biggest_index);
    00306   return hor;
    00307 }
    00308
    00310 static NWidgetBase *MakeAsdfgKeys(int *biggest_index)
    00311 {
    00312   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00313   int key_height = FONT_HEIGHT_NORMAL + 6;
    00314
    00315   AddKey(hor, key_height, 4, WWT_IMGBTN, WID_OSK_CAPS, SPR_OSK_CAPS, biggest_index);
    00316   for (int widnum = WID_OSK_ASDFG_FIRST; widnum <= WID_OSK_ASDFG_LAST; widnum++) {
    00317     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00318   }
    00319   return hor;
    00320 }
    00321
    00323 static NWidgetBase *MakeZxcvbKeys(int *biggest_index)
    00324 {
    00325   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00326   int key_height = FONT_HEIGHT_NORMAL + 6;
    00327
    00328   AddKey(hor, key_height, 3, WWT_IMGBTN, WID_OSK_SHIFT, SPR_OSK_SHIFT, biggest_index);
    00329   for (int widnum = WID_OSK_ZXCVB_FIRST; widnum <= WID_OSK_ZXCVB_LAST; widnum++) {
    00330     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00331   }
    00332   AddKey(hor, key_height, 1, NWID_SPACER, 0, 0, biggest_index);
    00333   return hor;
    00334 }
    00335
    00337 static NWidgetBase *MakeSpacebarKeys(int *biggest_index)
    00338 {
    00339   NWidgetHorizontal *hor = new NWidgetHorizontal();
    00340   int key_height = FONT_HEIGHT_NORMAL + 6;
    00341
    00342   AddKey(hor, key_height,  8, NWID_SPACER, 0, 0, biggest_index);
    00343   AddKey(hor, key_height, 13, WWT_PUSHTXTBTN, WID_OSK_SPACE, STR_EMPTY, biggest_index);
    00344   AddKey(hor, key_height,  3, NWID_SPACER, 0, 0, biggest_index);
    00345   AddKey(hor, key_height,  2, WWT_PUSHIMGBTN, WID_OSK_LEFT,  SPR_OSK_LEFT, biggest_index);
    00346   AddKey(hor, key_height,  2, WWT_PUSHIMGBTN, WID_OSK_RIGHT, SPR_OSK_RIGHT, biggest_index);
    00347   return hor;
    00348 }
    00349
    00350
    00351 static const NWidgetPart _nested_osk_widgets[] = {
    00352   NWidget(WWT_CAPTION, COLOUR_GREY, WID_OSK_CAPTION), SetDataTip(STR_WHITE_STRING, STR_NULL),
    00353   NWidget(WWT_PANEL, COLOUR_GREY),
    00354     NWidget(WWT_EDITBOX, COLOUR_GREY, WID_OSK_TEXT), SetMinimalSize(252, 12), SetPadding(2, 2, 2, 2),
    00355   EndContainer(),
    00356   NWidget(WWT_PANEL, COLOUR_GREY), SetPIP(5, 2, 3),
    00357     NWidgetFunction(MakeTopKeys), SetPadding(0, 3, 0, 3),
    00358     NWidgetFunction(MakeNumberKeys), SetPadding(0, 3, 0, 3),
    00359     NWidgetFunction(MakeQwertyKeys), SetPadding(0, 3, 0, 3),
    00360     NWidgetFunction(MakeAsdfgKeys), SetPadding(0, 3, 0, 3),
    00361     NWidgetFunction(MakeZxcvbKeys), SetPadding(0, 3, 0, 3),
    00362     NWidgetFunction(MakeSpacebarKeys), SetPadding(0, 3, 0, 3),
    00363   EndContainer(),
    00364 };
    00365
    00366 static const WindowDesc _osk_desc(
    00367   WDP_CENTER, 0, 0,
    00368   WC_OSK, WC_NONE,
    00369   WDF_UNCLICK_BUTTONS,
    00370   _nested_osk_widgets, lengthof(_nested_osk_widgets)
    00371 );
    00372
    00377 void GetKeyboardLayout()
    00378 {
    00379   char keyboard[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
    00380   char errormark[2][OSK_KEYBOARD_ENTRIES + 1]; // used for marking invalid chars
    00381   bool has_error = false; // true when an invalid char is detected
    00382
    00383   if (StrEmpty(_keyboard_opt[0])) {
    00384     GetString(keyboard[0], STR_OSK_KEYBOARD_LAYOUT, lastof(keyboard[0]));
    00385   } else {
    00386     strecpy(keyboard[0], _keyboard_opt[0], lastof(keyboard[0]));
    00387   }
    00388
    00389   if (StrEmpty(_keyboard_opt[1])) {
    00390     GetString(keyboard[1], STR_OSK_KEYBOARD_LAYOUT_CAPS, lastof(keyboard[1]));
    00391   } else {
    00392     strecpy(keyboard[1], _keyboard_opt[1], lastof(keyboard[1]));
    00393   }
    00394
    00395   for (uint j = 0; j < 2; j++) {
    00396     const char *kbd = keyboard[j];
    00397     bool ended = false;
    00398     for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
    00399       _keyboard[j][i] = Utf8Consume(&kbd);
    00400
    00401       /* Be lenient when the last characters are missing (is quite normal) */
    00402       if (_keyboard[j][i] == '\0' || ended) {
    00403         ended = true;
    00404         _keyboard[j][i] = ' ';
    00405         continue;
    00406       }
    00407
    00408       if (IsPrintable(_keyboard[j][i])) {
    00409         errormark[j][i] = ' ';
    00410       } else {
    00411         has_error = true;
    00412         errormark[j][i] = '^';
    00413         _keyboard[j][i] = ' ';
    00414       }
    00415     }
    00416   }
    00417
    00418   if (has_error) {
    00419     ShowInfoF("The keyboard layout you selected contains invalid chars. Please check those chars marked with ^.");
    00420     ShowInfoF("Normal keyboard:  %s", keyboard[0]);
    00421     ShowInfoF("                  %s", errormark[0]);
    00422     ShowInfoF("Caps Lock:        %s", keyboard[1]);
    00423     ShowInfoF("                  %s", errormark[1]);
    00424   }
    00425 }
    00426
    00436 void ShowOnScreenKeyboard(QueryStringBaseWindow *parent, int button, int cancel, int ok)
    00437 {
    00438   DeleteWindowById(WC_OSK, 0);
    00439
    00440   GetKeyboardLayout();
    00441   new OskWindow(&_osk_desc, parent, button, cancel, ok);
    00442 }
    00443
    00451 void UpdateOSKOriginalText(const QueryStringBaseWindow *parent, int button)
    00452 {
    00453   OskWindow *osk = dynamic_cast<OskWindow *>(FindWindowById(WC_OSK, 0));
    00454   if (osk == NULL || osk->qs != parent || osk->text_btn != button) return;
    00455
    00456   free(osk->orig_str_buf);
    00457   osk->orig_str_buf = strdup(osk->qs->text.buf);
    00458
    00459   osk->SetDirty();
    00460 }