osk_gui.cpp

Go to the documentation of this file.
00001 /* $Id: osk_gui.cpp 26024 2013-11-17 13:35:48Z rubidium $ */
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(const WindowDesc *desc, Window *parent, int button) : Window()
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(desc, 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 const WindowDesc _osk_desc(
00345   WDP_CENTER, 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 }