# osk_gui.cpp

Go to the documentation of this file.

    00001 /* $Id: osk_gui.cpp 19814 2010-05-13 10:14:29Z 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 "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
    00250   virtual void OnInvalidateData(int)
    00251   {
    00252     this->SetWidgetDirty(OSK_WIDGET_TEXT);
    00253   }
    00254 };
    00255
    00256 static const int HALF_KEY_WIDTH = 7;  // Width of 1/2 key in pixels.
    00257 static const int INTER_KEY_SPACE = 2; // Number of pixels between two keys.
    00258
    00270 static void AddKey(NWidgetHorizontal *hor, int height, int num_half, WidgetType widtype, int widnum, uint16 widdata, int *biggest_index)
    00271 {
    00272   int key_width = HALF_KEY_WIDTH + (INTER_KEY_SPACE + HALF_KEY_WIDTH) * (num_half - 1);
    00273
    00274   if (widtype == NWID_SPACER) {
    00275     if (!hor->IsEmpty()) key_width += INTER_KEY_SPACE;
    00276     NWidgetSpacer *spc = new NWidgetSpacer(key_width, height);
    00277     hor->Add(spc);
    00278   } else {
    00279     if (!hor->IsEmpty()) {
    00280       NWidgetSpacer *spc = new NWidgetSpacer(INTER_KEY_SPACE, height);
    00281       hor->Add(spc);
    00282     }
    00283     NWidgetLeaf *leaf = new NWidgetLeaf(widtype, COLOUR_GREY, widnum, widdata, STR_NULL);
    00284     leaf->SetMinimalSize(key_width, height);
    00285     hor->Add(leaf);
    00286   }
    00287
    00288   *biggest_index = max(*biggest_index, widnum);
    00289 }
    00290
    00292 static NWidgetBase *MakeTopKeys(int *biggest_index)
    00293 {
    00294   NWidgetHorizontal *hor = new NWidgetHorizontal();
    00295   int key_height = FONT_HEIGHT_NORMAL + 2;
    00296
    00297   AddKey(hor, key_height, 6 * 2, WWT_TEXTBTN,    OSK_WIDGET_CANCEL,    STR_BUTTON_CANCEL,  biggest_index);
    00298   AddKey(hor, key_height, 6 * 2, WWT_TEXTBTN,    OSK_WIDGET_OK,        STR_BUTTON_OK,      biggest_index);
    00299   AddKey(hor, key_height, 2 * 2, WWT_PUSHIMGBTN, OSK_WIDGET_BACKSPACE, SPR_OSK_BACKSPACE, biggest_index);
    00300   return hor;
    00301 }
    00302
    00304 static NWidgetBase *MakeNumberKeys(int *biggest_index)
    00305 {
    00306   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00307   int key_height = FONT_HEIGHT_NORMAL + 6;
    00308
    00309   for (int widnum = OSK_WIDGET_NUMBERS_FIRST; widnum <= OSK_WIDGET_NUMBERS_LAST; widnum++) {
    00310     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00311   }
    00312   return hor;
    00313 }
    00314
    00316 static NWidgetBase *MakeQwertyKeys(int *biggest_index)
    00317 {
    00318   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00319   int key_height = FONT_HEIGHT_NORMAL + 6;
    00320
    00321   AddKey(hor, key_height, 3, WWT_PUSHIMGBTN, OSK_WIDGET_SPECIAL, SPR_OSK_SPECIAL, biggest_index);
    00322   for (int widnum = OSK_WIDGET_QWERTY_FIRST; widnum <= OSK_WIDGET_QWERTY_LAST; widnum++) {
    00323     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00324   }
    00325   AddKey(hor, key_height, 1, NWID_SPACER, 0, 0, biggest_index);
    00326   return hor;
    00327 }
    00328
    00330 static NWidgetBase *MakeAsdfgKeys(int *biggest_index)
    00331 {
    00332   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00333   int key_height = FONT_HEIGHT_NORMAL + 6;
    00334
    00335   AddKey(hor, key_height, 4, WWT_IMGBTN, OSK_WIDGET_CAPS, SPR_OSK_CAPS, biggest_index);
    00336   for (int widnum = OSK_WIDGET_ASDFG_FIRST; widnum <= OSK_WIDGET_ASDFG_LAST; widnum++) {
    00337     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00338   }
    00339   return hor;
    00340 }
    00341
    00343 static NWidgetBase *MakeZxcvbKeys(int *biggest_index)
    00344 {
    00345   NWidgetHorizontal *hor = new NWidgetHorizontalLTR();
    00346   int key_height = FONT_HEIGHT_NORMAL + 6;
    00347
    00348   AddKey(hor, key_height, 3, WWT_IMGBTN, OSK_WIDGET_SHIFT, SPR_OSK_SHIFT, biggest_index);
    00349   for (int widnum = OSK_WIDGET_ZXCVB_FIRST; widnum <= OSK_WIDGET_ZXCVB_LAST; widnum++) {
    00350     AddKey(hor, key_height, 2, WWT_PUSHBTN, widnum, 0x0, biggest_index);
    00351   }
    00352   AddKey(hor, key_height, 1, NWID_SPACER, 0, 0, biggest_index);
    00353   return hor;
    00354 }
    00355
    00357 static NWidgetBase *MakeSpacebarKeys(int *biggest_index)
    00358 {
    00359   NWidgetHorizontal *hor = new NWidgetHorizontal();
    00360   int key_height = FONT_HEIGHT_NORMAL + 6;
    00361
    00362   AddKey(hor, key_height,  8, NWID_SPACER, 0, 0, biggest_index);
    00363   AddKey(hor, key_height, 13, WWT_PUSHTXTBTN, OSK_WIDGET_SPACE, STR_EMPTY, biggest_index);
    00364   AddKey(hor, key_height,  3, NWID_SPACER, 0, 0, biggest_index);
    00365   AddKey(hor, key_height,  2, WWT_PUSHIMGBTN, OSK_WIDGET_LEFT,  SPR_OSK_LEFT, biggest_index);
    00366   AddKey(hor, key_height,  2, WWT_PUSHIMGBTN, OSK_WIDGET_RIGHT, SPR_OSK_RIGHT, biggest_index);
    00367   return hor;
    00368 }
    00369
    00370
    00371 static const NWidgetPart _nested_osk_widgets[] = {
    00372   NWidget(WWT_CAPTION, COLOUR_GREY, OSK_WIDGET_CAPTION), SetDataTip(STR_WHITE_STRING, STR_NULL),
    00373   NWidget(WWT_PANEL, COLOUR_GREY),
    00374     NWidget(WWT_EDITBOX, COLOUR_GREY, OSK_WIDGET_TEXT), SetMinimalSize(252, 12), SetPadding(2, 2, 2, 2),
    00375   EndContainer(),
    00376   NWidget(WWT_PANEL, COLOUR_GREY), SetPIP(5, 2, 3),
    00377     NWidgetFunction(MakeTopKeys), SetPadding(0, 3, 0, 3),
    00378     NWidgetFunction(MakeNumberKeys), SetPadding(0, 3, 0, 3),
    00379     NWidgetFunction(MakeQwertyKeys), SetPadding(0, 3, 0, 3),
    00380     NWidgetFunction(MakeAsdfgKeys), SetPadding(0, 3, 0, 3),
    00381     NWidgetFunction(MakeZxcvbKeys), SetPadding(0, 3, 0, 3),
    00382     NWidgetFunction(MakeSpacebarKeys), SetPadding(0, 3, 0, 3),
    00383   EndContainer(),
    00384 };
    00385
    00386 static const WindowDesc _osk_desc(
    00387   WDP_CENTER, 0, 0,
    00388   WC_OSK, WC_NONE,
    00389   WDF_UNCLICK_BUTTONS,
    00390   _nested_osk_widgets, lengthof(_nested_osk_widgets)
    00391 );
    00392
    00397 void GetKeyboardLayout()
    00398 {
    00399   char keyboard[2][OSK_KEYBOARD_ENTRIES * 4 + 1];
    00400   char errormark[2][OSK_KEYBOARD_ENTRIES + 1]; // used for marking invalid chars
    00401   bool has_error = false; // true when an invalid char is detected
    00402
    00403   if (StrEmpty(_keyboard_opt[0])) {
    00404     GetString(keyboard[0], STR_OSK_KEYBOARD_LAYOUT, lastof(keyboard[0]));
    00405   } else {
    00406     strecpy(keyboard[0], _keyboard_opt[0], lastof(keyboard[0]));
    00407   }
    00408
    00409   if (StrEmpty(_keyboard_opt[1])) {
    00410     GetString(keyboard[1], STR_OSK_KEYBOARD_LAYOUT_CAPS, lastof(keyboard[1]));
    00411   } else {
    00412     strecpy(keyboard[1], _keyboard_opt[1], lastof(keyboard[1]));
    00413   }
    00414
    00415   for (uint j = 0; j < 2; j++) {
    00416     const char *kbd = keyboard[j];
    00417     bool ended = false;
    00418     for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) {
    00419       _keyboard[j][i] = Utf8Consume(&kbd);
    00420
    00421       /* Be lenient when the last characters are missing (is quite normal) */
    00422       if (_keyboard[j][i] == '\0' || ended) {
    00423         ended = true;
    00424         _keyboard[j][i] = ' ';
    00425         continue;
    00426       }
    00427
    00428       if (IsPrintable(_keyboard[j][i])) {
    00429         errormark[j][i] = ' ';
    00430       } else {
    00431         has_error = true;
    00432         errormark[j][i] = '^';
    00433         _keyboard[j][i] = ' ';
    00434       }
    00435     }
    00436   }
    00437
    00438   if (has_error) {
    00439     ShowInfoF("The keyboard layout you selected contains invalid chars. Please check those chars marked with ^.");
    00440     ShowInfoF("Normal keyboard:  %s", keyboard[0]);
    00441     ShowInfoF("                  %s", errormark[0]);
    00442     ShowInfoF("Caps Lock:        %s", keyboard[1]);
    00443     ShowInfoF("                  %s", errormark[1]);
    00444   }
    00445 }
    00446
    00456 void ShowOnScreenKeyboard(QueryStringBaseWindow *parent, int button, int cancel, int ok)
    00457 {
    00458   DeleteWindowById(WC_OSK, 0);
    00459
    00460   GetKeyboardLayout();
    00461   new OskWindow(&_osk_desc, parent, button, cancel, ok);
    00462 }
    00463
    00471 void UpdateOSKOriginalText(const QueryStringBaseWindow *parent, int button)
    00472 {
    00473   OskWindow *osk = dynamic_cast<OskWindow *>(FindWindowById(WC_OSK, 0));
    00474   if (osk == NULL || osk->qs != parent || osk->text_btn != button) return;
    00475
    00476   free(osk->orig_str_buf);
    00477   osk->orig_str_buf = strdup(osk->qs->text.buf);
    00478
    00479   osk->SetDirty();
    00480 }