osk_gui.cpp

Go to the documentation of this file.
00001 /* $Id: osk_gui.cpp 25102 2013-03-17 20:58:40Z 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 
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 }