osk_gui.cpp

Go to the documentation of this file.
00001 /* $Id: osk_gui.cpp 23550 2011-12-16 18:27:39Z 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   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 }