# 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 "table/sprites.h"
    00021 #include "table/strings.h"
    00022
    00024 enum OskWidgets {
    00025   OSK_WIDGET_CAPTION,
    00026   OSK_WIDGET_TEXT,
    00027   OSK_WIDGET_CANCEL,
    00028   OSK_WIDGET_OK,
    00029   OSK_WIDGET_BACKSPACE,
    00030   OSK_WIDGET_SPECIAL,
    00031   OSK_WIDGET_CAPS,
    00032   OSK_WIDGET_SHIFT,
    00033   OSK_WIDGET_SPACE,
    00034   OSK_WIDGET_LEFT,
    00035   OSK_WIDGET_RIGHT,
    00036   OSK_WIDGET_LETTERS,
    00037
    00038   OSK_WIDGET_NUMBERS_FIRST = OSK_WIDGET_LETTERS,
    00039   OSK_WIDGET_NUMBERS_LAST = OSK_WIDGET_NUMBERS_FIRST + 13,
    00040
    00041   OSK_WIDGET_QWERTY_FIRST,
    00042   OSK_WIDGET_QWERTY_LAST = OSK_WIDGET_QWERTY_FIRST + 11,
    00043
    00044   OSK_WIDGET_ASDFG_FIRST,
    00045   OSK_WIDGET_ASDFG_LAST = OSK_WIDGET_ASDFG_FIRST + 11,
    00046
    00047   OSK_WIDGET_ZXCVB_FIRST,
    00048   OSK_WIDGET_ZXCVB_LAST = OSK_WIDGET_ZXCVB_FIRST + 11,
    00049 };
    00050
    00051 char _keyboard_opt[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
    00052 static WChar _keyboard[2][OSK_KEYBOARD_ENTRIES];
    00053
    00054 enum KeyStateBits {
    00055   KEYS_NONE,
    00056   KEYS_SHIFT,
    00057   KEYS_CAPS
    00058 };
    00059 static byte _keystate = KEYS_NONE;
    00060
    00061 struct OskWindow : public Window {
    00062   StringID caption;
    00063   QueryString *qs;
    00064   int text_btn;
    00065   int ok_btn;
    00066   int cancel_btn;
    00067   Textbuf *text;
    00068   char *orig_str_buf;
    00069   bool shift;
    00070
    00071   OskWindow(const WindowDesc *desc, QueryStringBaseWindow *parent, int button, int cancel, int ok) : Window()
    00072   {
    00073     this->parent = parent;
    00074     assert(parent != NULL);
    00075
    00076     NWidgetCore *par_wid = parent->GetWidget<NWidgetCore>(button);
    00077     assert(par_wid != NULL);
    00078     this->caption = (par_wid->widget_data != STR_NULL) ? par_wid->widget_data : parent->caption;
    00079
    00080     this->qs         = parent;
    00081     this->text_btn   = button;
    00082     this->cancel_btn = cancel;
    00083     this->ok_btn     = ok;
    00084     this->text       = &parent->text;
    00085
    00086     /* make a copy in case we need to reset later */
    00087     this->orig_str_buf = strdup(this->qs->text.buf);
    00088
    00089     this->InitNested(desc, 0);
    00090
    00091     /* Not needed by default. */
    00092     this->DisableWidget(OSK_WIDGET_SPECIAL);
    00093
    00094     this->UpdateOskState();
    00095   }
    00096
    00097   ~OskWindow()
    00098   {
    00099     free(this->orig_str_buf);
    00100   }
    00101
    00107   void UpdateOskState()
    00108   {
    00109     this->shift = HasBit(_keystate, KEYS_CAPS) ^ HasBit(_keystate, KEYS_SHIFT);
    00110
    00111     for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
    00112       this->SetWidgetDisabledState(OSK_WIDGET_LETTERS + i,
    00113           !IsValidChar(_keyboard[this->shift][i], this->qs->afilter) || _keyboard[this->shift][i] == ' ');
    00114     }
    00115     this->SetWidgetDisabledState(OSK_WIDGET_SPACE, !IsValidChar(' ', this->qs->afilter));
    00116
    00117     this->LowerWidget(OSK_WIDGET_TEXT);
    00118     this->SetWidgetLoweredState(OSK_WIDGET_SHIFT, HasBit(_keystate, KEYS_SHIFT));
    00119     this->SetWidgetLoweredState(OSK_WIDGET_CAPS, HasBit(_keystate, KEYS_CAPS));
    00120   }
    00121
    00122   virtual void SetStringParameters(int widget) const
    00123   {
    00124     if (widget == OSK_WIDGET_CAPTION) SetDParam(0, this->caption);
    00125   }
    00126
    00127   virtual void DrawWidget(const Rect &r, int widget) const
    00128   {
    00129     if (widget < OSK_WIDGET_LETTERS) return;
    00130
    00131     widget -= OSK_WIDGET_LETTERS;
    00132     DrawCharCentered(_keyboard[this->shift][widget],
    00133       r.left + 8,
    00134       r.top + 3,
    00135       TC_BLACK);
    00136   }
    00137
    00138   virtual void OnPaint()
    00139   {
    00140     this->DrawWidgets();
    00141
    00142     this->qs->DrawEditBox(this, OSK_WIDGET_TEXT);
    00143   }
    00144
    00145   virtual void OnClick(Point pt, int widget, int click_count)
    00146   {
    00147     /* clicked a letter */
    00148     if (widget >= OSK_WIDGET_LETTERS) {
    00149       WChar c = _keyboard[this->shift][widget - OSK_WIDGET_LETTERS];
    00150
    00151       if (!IsValidChar(c, this->qs->afilter)) return;
    00152
    00153       if (InsertTextBufferChar(&this->qs->text, c)) this->InvalidateParent();
    00154
    00155       if (HasBit(_keystate, KEYS_SHIFT)) {
    00156         ToggleBit(_keystate, KEYS_SHIFT);
    00157         this->GetWidget<NWidgetCore>(OSK_WIDGET_SHIFT)->colour = HasBit(_keystate, KEYS_SHIFT) ? COLOUR_WHITE : COLOUR_GREY;
    00158         this->SetDirty();
    00159       }
    00160       return;
    00161     }
    00162
    00163     switch (widget) {
    00164       case OSK_WIDGET_TEXT:
    00165         /* Return focus to the parent widget and window. */
    00166         this->parent->SetFocusedWidget(this->text_btn);
    00167         SetFocusedWindow(this->parent);
    00168         break;
    00169
    00170       case OSK_WIDGET_BACKSPACE:
    00171         if (DeleteTextBufferChar(&this->qs->text, WKC_BACKSPACE)) this->InvalidateParent();
    00172         break;
    00173
    00174       case OSK_WIDGET_SPECIAL:
    00175         /*
    00176          * Anything device specific can go here.
    00177          * The button itself is hidden by default, and when you need it you
    00178          * can not hide it in the create event.
    00179          */
    00180         break;
    00181
    00182       case OSK_WIDGET_CAPS:
    00183         ToggleBit(_keystate, KEYS_CAPS);
    00184         this->UpdateOskState();
    00185         this->SetDirty();
    00186         break;
    00187
    00188       case OSK_WIDGET_SHIFT:
    00189         ToggleBit(_keystate, KEYS_SHIFT);
    00190         this->UpdateOskState();
    00191         this->SetDirty();
    00192         break;
    00193
    00194       case OSK_WIDGET_SPACE:
    00195         if (InsertTextBufferChar(&this->qs->text, ' ')) this->InvalidateParent();
    00196         break;
    00197
    00198       case OSK_WIDGET_LEFT:
    00199         if (MoveTextBufferPos(&this->qs->text, WKC_LEFT)) this->InvalidateParent();
    00200         break;
    00201
    00202       case OSK_WIDGET_RIGHT:
    00203         if (MoveTextBufferPos(&this->qs->text, WKC_RIGHT)) this->InvalidateParent();
    00204         break;
    00205
    00206       case OSK_WIDGET_OK:
    00207         if (this->qs->orig == NULL || strcmp(this->qs->text.buf, this->qs->orig) != 0) {
    00208           /* pass information by simulating a button press on parent window */
    00209           if (this->ok_btn != 0) {
    00210             this->parent->OnClick(pt, this->ok_btn, 1);
    00211             /* Window gets deleted when the parent window removes itself. */
    00212             return;
    00213           }
    00214         }
    00215         delete this;
    00216         break;
    00217
    00218       case OSK_WIDGET_CANCEL:
    00219         if (this->cancel_btn != 0) { // pass a cancel event to the parent window
    00220           this->parent->OnClick(pt, this->cancel_btn, 1);
    00221           /* Window gets deleted when the parent window removes itself. */
    00222           return;
    00223         } else { // or reset to original string
    00224           strcpy(qs->text.buf, this->orig_str_buf);
    00225           UpdateTextBufferSize(&qs->text);
    00226           MoveTextBufferPos(&qs->text, WKC_END);
    00227           this->InvalidateParent();
    00228           delete this;
    00229         }
    00230         break;
    00231     }
    00232   }
    00233
    00234   void InvalidateParent()
    00235   {
    00236     QueryStringBaseWindow *w = dynamic_cast<QueryStringBaseWindow*>(this->parent);
    00237     if (w != NULL) w->OnOSKInput(this->text_btn);
    00238
    00239     this->SetWidgetDirty(OSK_WIDGET_TEXT);
    00240     if (this->parent != NULL) this->parent->SetWidgetDirty(this->text_btn);
    00241   }
    00242
    00243   virtual void OnMouseLoop()
    00244   {
    00245     this->qs->HandleEditBox(this, OSK_WIDGET_TEXT);
    00246     /* make the caret of the parent window also blink */
    00247     this->parent->SetWidgetDirty(this->text_btn);
    00248   }
    00249
    00255   virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
    00256   {
    00257     if (!gui_scope) return;
    00258     this->SetWidgetDirty(OSK_WIDGET_TEXT);
    00259   }
    00260 };
    00261
    00262 static const int HALF_KEY_WIDTH = 7;  // Width of 1/2 key in pixels.
    00263 static const int INTER_KEY_SPACE = 2; // Number of pixels between two keys.
    00264
    00276 static void AddKey(NWidgetHorizontal *hor, int height, int num_half, WidgetType widtype, int widnum, uint16 widdata, int *biggest_index)
    00277 {
    00278   int key_width = HALF_KEY_WIDTH + (INTER_KEY_SPACE + HALF_KEY_WIDTH) * (num_half - 1);
    00279
    00280   if (widtype == NWID_SPACER) {
    00281     if (!hor->IsEmpty()) key_width += INTER_KEY_SPACE;
    00282     NWidgetSpacer *spc = new NWidgetSpacer(key_width, height);
    00283     hor->Add(spc);
    00284   } else {
    00285     if (!hor->IsEmpty()) {
    00286       NWidgetSpacer *spc = new NWidgetSpacer(INTER_KEY_SPACE, height);
    00287       hor->Add(spc);
    00288     }
    00289     NWidgetLeaf *leaf = new NWidgetLeaf(widtype, COLOUR_GREY, widnum, widdata, STR_NULL);
    00290     leaf->SetMinimalSize(key_width, height);
    00291     hor->Add(leaf);
    00292   }
    00293
    00294   *biggest_index = max(*biggest_index, widnum);
    00295 }
    00296
    00298 static NWidgetBase *MakeTopKeys(int *biggest_index)
    00299 {
    00300   NWidgetHorizontal *hor = new NWidgetHorizontal();
    00301   int key_height = FONT_HEIGHT_NORMAL + 2;
    00302
    00303   AddKey(hor, key_height, 6 * 2, WWT_TEXTBTN,    OSK_WIDGET_CANCEL,    STR_BUTTON_CANCEL,  biggest_index);
    00304   AddKey(hor, key_height, 6 * 2, WWT_TEXTBTN,    OSK_WIDGET_OK,        STR_BUTTON_OK,      biggest_index);
    00305   AddKey(hor, key_height, 2 * 2, WWT_PUSHIMGBTN, OSK_WIDGET_BACKSPACE, SPR_OSK_BACKSPACE, biggest_index);
    00306   return hor;
    00307 }
    00308
    00310 static NWidgetBase *MakeNumberKeys(int *biggest_index)
    00311 {
    00312   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00313   int key_height = FONT_HEIGHT_NORMAL + 6;
    00314
    00315   for (int widnum = OSK_WIDGET_NUMBERS_FIRST; widnum <= OSK_WIDGET_NUMBERS_LAST; widnum++) {
    00316     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00317   }
    00318   return hor;
    00319 }
    00320
    00322 static NWidgetBase *MakeQwertyKeys(int *biggest_index)
    00323 {
    00324   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00325   int key_height = FONT_HEIGHT_NORMAL + 6;
    00326
    00327   AddKey(hor, key_height, 3, WWT_PUSHIMGBTN, OSK_WIDGET_SPECIAL, SPR_OSK_SPECIAL, biggest_index);
    00328   for (int widnum = OSK_WIDGET_QWERTY_FIRST; widnum <= OSK_WIDGET_QWERTY_LAST; widnum++) {
    00329     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00330   }
    00331   AddKey(hor, key_height, 1, NWID_SPACER, 0, 0, biggest_index);
    00332   return hor;
    00333 }
    00334
    00336 static NWidgetBase *MakeAsdfgKeys(int *biggest_index)
    00337 {
    00338   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00339   int key_height = FONT_HEIGHT_NORMAL + 6;
    00340
    00341   AddKey(hor, key_height, 4, WWT_IMGBTN, OSK_WIDGET_CAPS, SPR_OSK_CAPS, biggest_index);
    00342   for (int widnum = OSK_WIDGET_ASDFG_FIRST; widnum <= OSK_WIDGET_ASDFG_LAST; widnum++) {
    00343     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00344   }
    00345   return hor;
    00346 }
    00347
    00349 static NWidgetBase *MakeZxcvbKeys(int *biggest_index)
    00350 {
    00351   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00352   int key_height = FONT_HEIGHT_NORMAL + 6;
    00353
    00354   AddKey(hor, key_height, 3, WWT_IMGBTN, OSK_WIDGET_SHIFT, SPR_OSK_SHIFT, biggest_index);
    00355   for (int widnum = OSK_WIDGET_ZXCVB_FIRST; widnum <= OSK_WIDGET_ZXCVB_LAST; widnum++) {
    00356     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00357   }
    00358   AddKey(hor, key_height, 1, NWID_SPACER, 0, 0, biggest_index);
    00359   return hor;
    00360 }
    00361
    00363 static NWidgetBase *MakeSpacebarKeys(int *biggest_index)
    00364 {
    00365   NWidgetHorizontal *hor = new NWidgetHorizontal();
    00366   int key_height = FONT_HEIGHT_NORMAL + 6;
    00367
    00368   AddKey(hor, key_height,  8, NWID_SPACER, 0, 0, biggest_index);
    00369   AddKey(hor, key_height, 13, WWT_PUSHTXTBTN, OSK_WIDGET_SPACE, STR_EMPTY, biggest_index);
    00370   AddKey(hor, key_height,  3, NWID_SPACER, 0, 0, biggest_index);
    00371   AddKey(hor, key_height,  2, WWT_PUSHIMGBTN, OSK_WIDGET_LEFT,  SPR_OSK_LEFT, biggest_index);
    00372   AddKey(hor, key_height,  2, WWT_PUSHIMGBTN, OSK_WIDGET_RIGHT, SPR_OSK_RIGHT, biggest_index);
    00373   return hor;
    00374 }
    00375
    00376
    00377 static const NWidgetPart _nested_osk_widgets[] = {
    00378   NWidget(WWT_CAPTION, COLOUR_GREY, OSK_WIDGET_CAPTION), SetDataTip(STR_WHITE_STRING, STR_NULL),
    00379   NWidget(WWT_PANEL, COLOUR_GREY),
    00380     NWidget(WWT_EDITBOX, COLOUR_GREY, OSK_WIDGET_TEXT), SetMinimalSize(252, 12), SetPadding(2, 2, 2, 2),
    00381   EndContainer(),
    00382   NWidget(WWT_PANEL, COLOUR_GREY), SetPIP(5, 2, 3),
    00383     NWidgetFunction(MakeTopKeys), SetPadding(0, 3, 0, 3),
    00384     NWidgetFunction(MakeNumberKeys), SetPadding(0, 3, 0, 3),
    00385     NWidgetFunction(MakeQwertyKeys), SetPadding(0, 3, 0, 3),
    00386     NWidgetFunction(MakeAsdfgKeys), SetPadding(0, 3, 0, 3),
    00387     NWidgetFunction(MakeZxcvbKeys), SetPadding(0, 3, 0, 3),
    00388     NWidgetFunction(MakeSpacebarKeys), SetPadding(0, 3, 0, 3),
    00389   EndContainer(),
    00390 };
    00391
    00392 static const WindowDesc _osk_desc(
    00393   WDP_CENTER, 0, 0,
    00394   WC_OSK, WC_NONE,
    00395   WDF_UNCLICK_BUTTONS,
    00396   _nested_osk_widgets, lengthof(_nested_osk_widgets)
    00397 );
    00398
    00403 void GetKeyboardLayout()
    00404 {
    00405   char keyboard[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
    00406   char errormark[2][OSK_KEYBOARD_ENTRIES + 1]; // used for marking invalid chars
    00407   bool has_error = false; // true when an invalid char is detected
    00408
    00409   if (StrEmpty(_keyboard_opt[0])) {
    00410     GetString(keyboard[0], STR_OSK_KEYBOARD_LAYOUT, lastof(keyboard[0]));
    00411   } else {
    00412     strecpy(keyboard[0], _keyboard_opt[0], lastof(keyboard[0]));
    00413   }
    00414
    00415   if (StrEmpty(_keyboard_opt[1])) {
    00416     GetString(keyboard[1], STR_OSK_KEYBOARD_LAYOUT_CAPS, lastof(keyboard[1]));
    00417   } else {
    00418     strecpy(keyboard[1], _keyboard_opt[1], lastof(keyboard[1]));
    00419   }
    00420
    00421   for (uint j = 0; j < 2; j++) {
    00422     const char *kbd = keyboard[j];
    00423     bool ended = false;
    00424     for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
    00425       _keyboard[j][i] = Utf8Consume(&kbd);
    00426
    00427       /* Be lenient when the last characters are missing (is quite normal) */
    00428       if (_keyboard[j][i] == '\0' || ended) {
    00429         ended = true;
    00430         _keyboard[j][i] = ' ';
    00431         continue;
    00432       }
    00433
    00434       if (IsPrintable(_keyboard[j][i])) {
    00435         errormark[j][i] = ' ';
    00436       } else {
    00437         has_error = true;
    00438         errormark[j][i] = '^';
    00439         _keyboard[j][i] = ' ';
    00440       }
    00441     }
    00442   }
    00443
    00444   if (has_error) {
    00445     ShowInfoF("The keyboard layout you selected contains invalid chars. Please check those chars marked with ^.");
    00446     ShowInfoF("Normal keyboard:  %s", keyboard[0]);
    00447     ShowInfoF("                  %s", errormark[0]);
    00448     ShowInfoF("Caps Lock:        %s", keyboard[1]);
    00449     ShowInfoF("                  %s", errormark[1]);
    00450   }
    00451 }
    00452
    00462 void ShowOnScreenKeyboard(QueryStringBaseWindow *parent, int button, int cancel, int ok)
    00463 {
    00464   DeleteWindowById(WC_OSK, 0);
    00465
    00466   GetKeyboardLayout();
    00467   new OskWindow(&_osk_desc, parent, button, cancel, ok);
    00468 }
    00469
    00477 void UpdateOSKOriginalText(const QueryStringBaseWindow *parent, int button)
    00478 {
    00479   OskWindow *osk = dynamic_cast<OskWindow *>(FindWindowById(WC_OSK, 0));
    00480   if (osk == NULL || osk->qs != parent || osk->text_btn != button) return;
    00481
    00482   free(osk->orig_str_buf);
    00483   osk->orig_str_buf = strdup(osk->qs->text.buf);
    00484
    00485   osk->SetDirty();
    00486 }