osk_gui.cpp

Go to the documentation of this file.
00001 /* $Id: osk_gui.cpp 18966 2010-01-30 18:34:48Z frosch $ */
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 {
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 }

Generated on Sat Nov 20 20:59:06 2010 for OpenTTD by  doxygen 1.6.1