# osk_gui.cpp

Go to the documentation of this file.

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