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