# 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   Textbuf *text;
    00040   char *orig_str_buf;
    00041   bool shift;
    00042
    00043   OskWindow(const WindowDesc *desc, Window *parent, int button) : Window()
    00044   {
    00045     this->parent = parent;
    00046     assert(parent != NULL);
    00047
    00048     NWidgetCore *par_wid = parent->GetWidget<NWidgetCore>(button);
    00049     assert(par_wid != NULL);
    00050
    00051     assert(parent->querystrings.Contains(button));
    00052     this->qs         = parent->querystrings.Find(button)->second;
    00053     this->caption = (par_wid->widget_data != STR_NULL) ? par_wid->widget_data : this->qs->caption;
    00054     this->text_btn   = button;
    00055     this->text       = &this->qs->text;
    00056     this->querystrings[WID_OSK_TEXT] = this->qs;
    00057
    00058     /* make a copy in case we need to reset later */
    00059     this->orig_str_buf = strdup(this->qs->text.buf);
    00060
    00061     this->InitNested(desc, 0);
    00062     this->SetFocusedWidget(WID_OSK_TEXT);
    00063
    00064     /* Not needed by default. */
    00065     this->DisableWidget(WID_OSK_SPECIAL);
    00066
    00067     this->UpdateOskState();
    00068   }
    00069
    00070   ~OskWindow()
    00071   {
    00072     free(this->orig_str_buf);
    00073   }
    00074
    00080   void UpdateOskState()
    00081   {
    00082     this->shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
    00083
    00084     for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
    00085       this->SetWidgetDisabledState(WID_OSK_LETTERS + i,
    00086           !IsValidChar(_keyboard[this->shift][i], this->qs->text.afilter) || _keyboard[this->shift][i] == ' ');
    00087     }
    00088     this->SetWidgetDisabledState(WID_OSK_SPACE, !IsValidChar(' ', this->qs->text.afilter));
    00089
    00090     this->SetWidgetLoweredState(WID_OSK_SHIFT, HasBit(_keystate, KEYS_SHIFT));
    00091     this->SetWidgetLoweredState(WID_OSK_CAPS, HasBit(_keystate, KEYS_CAPS));
    00092   }
    00093
    00094   virtual void SetStringParameters(int widget) const
    00095   {
    00096     if (widget == WID_OSK_CAPTION) SetDParam(0, this->caption);
    00097   }
    00098
    00099   virtual void DrawWidget(const Rect &r, int widget) const
    00100   {
    00101     if (widget < WID_OSK_LETTERS) return;
    00102
    00103     widget -= WID_OSK_LETTERS;
    00104     DrawCharCentered(_keyboard[this->shift][widget],
    00105       r.left + 8,
    00106       r.top + 3,
    00107       TC_BLACK);
    00108   }
    00109
    00110   virtual void OnClick(Point pt, int widget, int click_count)
    00111   {
    00112     /* clicked a letter */
    00113     if (widget >= WID_OSK_LETTERS) {
    00114       WChar c = _keyboard[this->shift][widget - WID_OSK_LETTERS];
    00115
    00116       if (!IsValidChar(c, this->qs->text.afilter)) return;
    00117
    00118       if (this->qs->text.InsertChar(c)) this->OnEditboxChanged(WID_OSK_TEXT);
    00119
    00120       if (HasBit(_keystate, KEYS_SHIFT)) {
    00121         ToggleBit(_keystate, KEYS_SHIFT);
    00122         this->UpdateOskState();
    00123         this->SetDirty();
    00124       }
    00125       return;
    00126     }
    00127
    00128     switch (widget) {
    00129       case WID_OSK_BACKSPACE:
    00130         if (this->qs->text.DeleteChar(WKC_BACKSPACE)) this->OnEditboxChanged(WID_OSK_TEXT);
    00131         break;
    00132
    00133       case WID_OSK_SPECIAL:
    00134         /*
    00135          * Anything device specific can go here.
    00136          * The button itself is hidden by default, and when you need it you
    00137          * can not hide it in the create event.
    00138          */
    00139         break;
    00140
    00141       case WID_OSK_CAPS:
    00142         ToggleBit(_keystate, KEYS_CAPS);
    00143         this->UpdateOskState();
    00144         this->SetDirty();
    00145         break;
    00146
    00147       case WID_OSK_SHIFT:
    00148         ToggleBit(_keystate, KEYS_SHIFT);
    00149         this->UpdateOskState();
    00150         this->SetDirty();
    00151         break;
    00152
    00153       case WID_OSK_SPACE:
    00154         if (this->qs->text.InsertChar(' ')) this->OnEditboxChanged(WID_OSK_TEXT);
    00155         break;
    00156
    00157       case WID_OSK_LEFT:
    00158         if (this->qs->text.MovePos(WKC_LEFT)) this->InvalidateData();
    00159         break;
    00160
    00161       case WID_OSK_RIGHT:
    00162         if (this->qs->text.MovePos(WKC_RIGHT)) this->InvalidateData();
    00163         break;
    00164
    00165       case WID_OSK_OK:
    00166         if (this->qs->orig == NULL || strcmp(this->qs->text.buf, this->qs->orig) != 0) {
    00167           /* pass information by simulating a button press on parent window */
    00168           if (this->qs->ok_button >= 0) {
    00169             this->parent->OnClick(pt, this->qs->ok_button, 1);
    00170             /* Window gets deleted when the parent window removes itself. */
    00171             return;
    00172           }
    00173         }
    00174         delete this;
    00175         break;
    00176
    00177       case WID_OSK_CANCEL:
    00178         if (this->qs->cancel_button >= 0) { // pass a cancel event to the parent window
    00179           this->parent->OnClick(pt, this->qs->cancel_button, 1);
    00180           /* Window gets deleted when the parent window removes itself. */
    00181           return;
    00182         } else { // or reset to original string
    00183           qs->text.Assign(this->orig_str_buf);
    00184           qs->text.MovePos(WKC_END);
    00185           this->OnEditboxChanged(WID_OSK_TEXT);
    00186           delete this;
    00187         }
    00188         break;
    00189     }
    00190   }
    00191
    00192   virtual void OnEditboxChanged(int widget)
    00193   {
    00194     this->SetWidgetDirty(WID_OSK_TEXT);
    00195     this->parent->OnEditboxChanged(this->text_btn);
    00196     this->parent->SetWidgetDirty(this->text_btn);
    00197   }
    00198
    00199   virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
    00200   {
    00201     if (!gui_scope) return;
    00202     this->SetWidgetDirty(WID_OSK_TEXT);
    00203     this->parent->SetWidgetDirty(this->text_btn);
    00204   }
    00205
    00206   virtual void OnFocusLost()
    00207   {
    00208     delete this;
    00209   }
    00210 };
    00211
    00212 static const int HALF_KEY_WIDTH = 7;  // Width of 1/2 key in pixels.
    00213 static const int INTER_KEY_SPACE = 2; // Number of pixels between two keys.
    00214
    00226 static void AddKey(NWidgetHorizontal *hor, int height, int num_half, WidgetType widtype, int widnum, uint16 widdata, int *biggest_index)
    00227 {
    00228   int key_width = HALF_KEY_WIDTH + (INTER_KEY_SPACE + HALF_KEY_WIDTH) * (num_half - 1);
    00229
    00230   if (widtype == NWID_SPACER) {
    00231     if (!hor->IsEmpty()) key_width += INTER_KEY_SPACE;
    00232     NWidgetSpacer *spc = new NWidgetSpacer(key_width, height);
    00233     hor->Add(spc);
    00234   } else {
    00235     if (!hor->IsEmpty()) {
    00236       NWidgetSpacer *spc = new NWidgetSpacer(INTER_KEY_SPACE, height);
    00237       hor->Add(spc);
    00238     }
    00239     NWidgetLeaf *leaf = new NWidgetLeaf(widtype, COLOUR_GREY, widnum, widdata, STR_NULL);
    00240     leaf->SetMinimalSize(key_width, height);
    00241     hor->Add(leaf);
    00242   }
    00243
    00244   *biggest_index = max(*biggest_index, widnum);
    00245 }
    00246
    00248 static NWidgetBase *MakeTopKeys(int *biggest_index)
    00249 {
    00250   NWidgetHorizontal *hor = new NWidgetHorizontal();
    00251   int key_height = FONT_HEIGHT_NORMAL + 2;
    00252
    00253   AddKey(hor, key_height, 6 * 2, WWT_TEXTBTN,    WID_OSK_CANCEL,    STR_BUTTON_CANCEL,  biggest_index);
    00254   AddKey(hor, key_height, 6 * 2, WWT_TEXTBTN,    WID_OSK_OK,        STR_BUTTON_OK,      biggest_index);
    00255   AddKey(hor, key_height, 2 * 2, WWT_PUSHIMGBTN, WID_OSK_BACKSPACE, SPR_OSK_BACKSPACE, biggest_index);
    00256   return hor;
    00257 }
    00258
    00260 static NWidgetBase *MakeNumberKeys(int *biggest_index)
    00261 {
    00262   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00263   int key_height = FONT_HEIGHT_NORMAL + 6;
    00264
    00265   for (int widnum = WID_OSK_NUMBERS_FIRST; widnum <= WID_OSK_NUMBERS_LAST; widnum++) {
    00266     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00267   }
    00268   return hor;
    00269 }
    00270
    00272 static NWidgetBase *MakeQwertyKeys(int *biggest_index)
    00273 {
    00274   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00275   int key_height = FONT_HEIGHT_NORMAL + 6;
    00276
    00277   AddKey(hor, key_height, 3, WWT_PUSHIMGBTN, WID_OSK_SPECIAL, SPR_OSK_SPECIAL, biggest_index);
    00278   for (int widnum = WID_OSK_QWERTY_FIRST; widnum <= WID_OSK_QWERTY_LAST; widnum++) {
    00279     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00280   }
    00281   AddKey(hor, key_height, 1, NWID_SPACER, 0, 0, biggest_index);
    00282   return hor;
    00283 }
    00284
    00286 static NWidgetBase *MakeAsdfgKeys(int *biggest_index)
    00287 {
    00288   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00289   int key_height = FONT_HEIGHT_NORMAL + 6;
    00290
    00291   AddKey(hor, key_height, 4, WWT_IMGBTN, WID_OSK_CAPS, SPR_OSK_CAPS, biggest_index);
    00292   for (int widnum = WID_OSK_ASDFG_FIRST; widnum <= WID_OSK_ASDFG_LAST; widnum++) {
    00293     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00294   }
    00295   return hor;
    00296 }
    00297
    00299 static NWidgetBase *MakeZxcvbKeys(int *biggest_index)
    00300 {
    00301   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00302   int key_height = FONT_HEIGHT_NORMAL + 6;
    00303
    00304   AddKey(hor, key_height, 3, WWT_IMGBTN, WID_OSK_SHIFT, SPR_OSK_SHIFT, biggest_index);
    00305   for (int widnum = WID_OSK_ZXCVB_FIRST; widnum <= WID_OSK_ZXCVB_LAST; widnum++) {
    00306     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00307   }
    00308   AddKey(hor, key_height, 1, NWID_SPACER, 0, 0, biggest_index);
    00309   return hor;
    00310 }
    00311
    00313 static NWidgetBase *MakeSpacebarKeys(int *biggest_index)
    00314 {
    00315   NWidgetHorizontal *hor = new NWidgetHorizontal();
    00316   int key_height = FONT_HEIGHT_NORMAL + 6;
    00317
    00318   AddKey(hor, key_height,  8, NWID_SPACER, 0, 0, biggest_index);
    00319   AddKey(hor, key_height, 13, WWT_PUSHTXTBTN, WID_OSK_SPACE, STR_EMPTY, biggest_index);
    00320   AddKey(hor, key_height,  3, NWID_SPACER, 0, 0, biggest_index);
    00321   AddKey(hor, key_height,  2, WWT_PUSHIMGBTN, WID_OSK_LEFT,  SPR_OSK_LEFT, biggest_index);
    00322   AddKey(hor, key_height,  2, WWT_PUSHIMGBTN, WID_OSK_RIGHT, SPR_OSK_RIGHT, biggest_index);
    00323   return hor;
    00324 }
    00325
    00326
    00327 static const NWidgetPart _nested_osk_widgets[] = {
    00328   NWidget(WWT_CAPTION, COLOUR_GREY, WID_OSK_CAPTION), SetDataTip(STR_WHITE_STRING, STR_NULL),
    00329   NWidget(WWT_PANEL, COLOUR_GREY),
    00330     NWidget(WWT_EDITBOX, COLOUR_GREY, WID_OSK_TEXT), SetMinimalSize(252, 12), SetPadding(2, 2, 2, 2),
    00331   EndContainer(),
    00332   NWidget(WWT_PANEL, COLOUR_GREY), SetPIP(5, 2, 3),
    00333     NWidgetFunction(MakeTopKeys), SetPadding(0, 3, 0, 3),
    00334     NWidgetFunction(MakeNumberKeys), SetPadding(0, 3, 0, 3),
    00335     NWidgetFunction(MakeQwertyKeys), SetPadding(0, 3, 0, 3),
    00336     NWidgetFunction(MakeAsdfgKeys), SetPadding(0, 3, 0, 3),
    00337     NWidgetFunction(MakeZxcvbKeys), SetPadding(0, 3, 0, 3),
    00338     NWidgetFunction(MakeSpacebarKeys), SetPadding(0, 3, 0, 3),
    00339   EndContainer(),
    00340 };
    00341
    00342 static const WindowDesc _osk_desc(
    00343   WDP_CENTER, 0, 0,
    00344   WC_OSK, WC_NONE,
    00345   0,
    00346   _nested_osk_widgets, lengthof(_nested_osk_widgets)
    00347 );
    00348
    00353 void GetKeyboardLayout()
    00354 {
    00355   char keyboard[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
    00356   char errormark[2][OSK_KEYBOARD_ENTRIES + 1]; // used for marking invalid chars
    00357   bool has_error = false; // true when an invalid char is detected
    00358
    00359   if (StrEmpty(_keyboard_opt[0])) {
    00360     GetString(keyboard[0], STR_OSK_KEYBOARD_LAYOUT, lastof(keyboard[0]));
    00361   } else {
    00362     strecpy(keyboard[0], _keyboard_opt[0], lastof(keyboard[0]));
    00363   }
    00364
    00365   if (StrEmpty(_keyboard_opt[1])) {
    00366     GetString(keyboard[1], STR_OSK_KEYBOARD_LAYOUT_CAPS, lastof(keyboard[1]));
    00367   } else {
    00368     strecpy(keyboard[1], _keyboard_opt[1], lastof(keyboard[1]));
    00369   }
    00370
    00371   for (uint j = 0; j < 2; j++) {
    00372     const char *kbd = keyboard[j];
    00373     bool ended = false;
    00374     for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
    00375       _keyboard[j][i] = Utf8Consume(&kbd);
    00376
    00377       /* Be lenient when the last characters are missing (is quite normal) */
    00378       if (_keyboard[j][i] == '\0' || ended) {
    00379         ended = true;
    00380         _keyboard[j][i] = ' ';
    00381         continue;
    00382       }
    00383
    00384       if (IsPrintable(_keyboard[j][i])) {
    00385         errormark[j][i] = ' ';
    00386       } else {
    00387         has_error = true;
    00388         errormark[j][i] = '^';
    00389         _keyboard[j][i] = ' ';
    00390       }
    00391     }
    00392   }
    00393
    00394   if (has_error) {
    00395     ShowInfoF("The keyboard layout you selected contains invalid chars. Please check those chars marked with ^.");
    00396     ShowInfoF("Normal keyboard:  %s", keyboard[0]);
    00397     ShowInfoF("                  %s", errormark[0]);
    00398     ShowInfoF("Caps Lock:        %s", keyboard[1]);
    00399     ShowInfoF("                  %s", errormark[1]);
    00400   }
    00401 }
    00402
    00408 void ShowOnScreenKeyboard(Window *parent, int button)
    00409 {
    00410   DeleteWindowById(WC_OSK, 0);
    00411
    00412   GetKeyboardLayout();
    00413   new OskWindow(&_osk_desc, parent, button);
    00414 }
    00415
    00423 void UpdateOSKOriginalText(const Window *parent, int button)
    00424 {
    00425   OskWindow *osk = dynamic_cast<OskWindow *>(FindWindowById(WC_OSK, 0));
    00426   if (osk == NULL || osk->parent != parent || osk->text_btn != button) return;
    00427
    00428   free(osk->orig_str_buf);
    00429   osk->orig_str_buf = strdup(osk->qs->text.buf);
    00430
    00431   osk->SetDirty();
    00432 }
    00433
    00440 bool IsOSKOpenedFor(const Window *w, int button)
    00441 {
    00442   OskWindow *osk = dynamic_cast<OskWindow *>(FindWindowById(WC_OSK, 0));
    00443   return osk != NULL && osk->parent == w && osk->text_btn == button;
    00444 }