signs_gui.cpp

Go to the documentation of this file.
00001 /* $Id: signs_gui.cpp 18588 2009-12-21 16:24:29Z alberth $ */
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 "company_gui.h"
00014 #include "company_func.h"
00015 #include "signs_base.h"
00016 #include "signs_func.h"
00017 #include "debug.h"
00018 #include "command_func.h"
00019 #include "strings_func.h"
00020 #include "window_func.h"
00021 #include "map_func.h"
00022 #include "gfx_func.h"
00023 #include "viewport_func.h"
00024 #include "querystring_gui.h"
00025 #include "sortlist_type.h"
00026 #include "string_func.h"
00027 
00028 #include "table/strings.h"
00029 #include "table/sprites.h"
00030 
00031 struct SignList {
00032   typedef GUIList<const Sign *> GUISignList;
00033 
00034   static const Sign *last_sign;
00035   GUISignList signs;
00036 
00037   void BuildSignsList()
00038   {
00039     if (!this->signs.NeedRebuild()) return;
00040 
00041     DEBUG(misc, 3, "Building sign list");
00042 
00043     this->signs.Clear();
00044 
00045     const Sign *si;
00046     FOR_ALL_SIGNS(si) *this->signs.Append() = si;
00047 
00048     this->signs.Compact();
00049     this->signs.RebuildDone();
00050   }
00051 
00053   static int CDECL SignNameSorter(const Sign * const *a, const Sign * const *b)
00054   {
00055     static char buf_cache[64];
00056     char buf[64];
00057 
00058     SetDParam(0, (*a)->index);
00059     GetString(buf, STR_SIGN_NAME, lastof(buf));
00060 
00061     if (*b != last_sign) {
00062       last_sign = *b;
00063       SetDParam(0, (*b)->index);
00064       GetString(buf_cache, STR_SIGN_NAME, lastof(buf_cache));
00065     }
00066 
00067     return strcasecmp(buf, buf_cache);
00068   }
00069 
00070   void SortSignsList()
00071   {
00072     if (!this->signs.Sort(&SignNameSorter)) return;
00073 
00074     /* Reset the name sorter sort cache */
00075     this->last_sign = NULL;
00076   }
00077 };
00078 
00079 const Sign *SignList::last_sign = NULL;
00080 
00082 enum SignListWidgets {
00083   SLW_CAPTION,
00084   SLW_LIST,
00085   SLW_SCROLLBAR,
00086 };
00087 
00088 struct SignListWindow : Window, SignList {
00089   int text_offset; // Offset of the sign text relative to the left edge of the SLW_LIST widget.
00090 
00091   SignListWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
00092   {
00093     this->InitNested(desc, window_number);
00094 
00095     /* Create initial list. */
00096     this->signs.ForceRebuild();
00097     this->signs.ForceResort();
00098     this->BuildSignsList();
00099     this->SortSignsList();
00100     this->vscroll.SetCount(this->signs.Length());
00101   }
00102 
00103   virtual void OnPaint()
00104   {
00105     this->DrawWidgets();
00106   }
00107 
00108   virtual void DrawWidget(const Rect &r, int widget) const
00109   {
00110     switch (widget) {
00111       case SLW_LIST: {
00112         uint y = r.top + WD_FRAMERECT_TOP; // Offset from top of widget.
00113         /* No signs? */
00114         if (this->vscroll.GetCount() == 0) {
00115           DrawString(r.left + WD_FRAMETEXT_LEFT, r.right, y, STR_STATION_LIST_NONE);
00116           return;
00117         }
00118 
00119         bool rtl = _dynlang.text_dir == TD_RTL;
00120         int sprite_offset_y = (FONT_HEIGHT_NORMAL - 10) / 2 + 1;
00121         uint icon_left  = 4 + (rtl ? r.right - this->text_offset : r.left);
00122         uint text_left  = r.left + (rtl ? WD_FRAMERECT_LEFT : this->text_offset);
00123         uint text_right = r.right - (rtl ? this->text_offset : WD_FRAMERECT_RIGHT);
00124 
00125         /* At least one sign available. */
00126         for (uint16 i = this->vscroll.GetPosition(); this->vscroll.IsVisible(i) && i < this->vscroll.GetCount(); i++) {
00127           const Sign *si = this->signs[i];
00128 
00129           if (si->owner != OWNER_NONE) DrawCompanyIcon(si->owner, icon_left, y + sprite_offset_y);
00130 
00131           SetDParam(0, si->index);
00132           DrawString(text_left, text_right, y, STR_SIGN_NAME, TC_YELLOW);
00133           y += this->resize.step_height;
00134         }
00135         break;
00136       }
00137     }
00138   }
00139 
00140   virtual void SetStringParameters(int widget) const
00141   {
00142     if (widget == SLW_CAPTION) SetDParam(0, this->vscroll.GetCount());
00143   }
00144 
00145   virtual void OnClick(Point pt, int widget)
00146   {
00147     if (widget == SLW_LIST) {
00148       uint id_v = (pt.y - this->GetWidget<NWidgetBase>(SLW_LIST)->pos_y - WD_FRAMERECT_TOP) / this->resize.step_height;
00149 
00150       if (id_v >= this->vscroll.GetCapacity()) return;
00151       id_v += this->vscroll.GetPosition();
00152       if (id_v >= this->vscroll.GetCount()) return;
00153 
00154       const Sign *si = this->signs[id_v];
00155       ScrollMainWindowToTile(TileVirtXY(si->x, si->y));
00156     }
00157   }
00158 
00159   virtual void OnResize()
00160   {
00161     this->vscroll.SetCapacityFromWidget(this, SLW_LIST, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM);
00162   }
00163 
00164   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
00165   {
00166     switch (widget) {
00167       case SLW_LIST: {
00168         Dimension spr_dim = GetSpriteSize(SPR_COMPANY_ICON);
00169         this->text_offset = WD_FRAMETEXT_LEFT + spr_dim.width + 2; // 2 pixels space between icon and the sign text.
00170         resize->height = max<uint>(FONT_HEIGHT_NORMAL, GetSpriteSize(SPR_COMPANY_ICON).height);
00171         Dimension d = {this->text_offset + MAX_LENGTH_SIGN_NAME_PIXELS + WD_FRAMETEXT_RIGHT, WD_FRAMERECT_TOP + 5 * resize->height + WD_FRAMERECT_BOTTOM};
00172         *size = maxdim(*size, d);
00173       } break;
00174 
00175       case SLW_CAPTION:
00176         SetDParam(0, max<size_t>(1000, Sign::GetPoolSize()));
00177         *size = GetStringBoundingBox(STR_SIGN_LIST_CAPTION);
00178         size->height += padding.height;
00179         size->width  += padding.width;
00180         break;
00181     }
00182   }
00183 
00184   virtual void OnInvalidateData(int data)
00185   {
00186     if (data == 0) { // New or deleted sign.
00187       this->signs.ForceRebuild();
00188       this->BuildSignsList();
00189       this->SetWidgetDirty(SLW_CAPTION);
00190       this->vscroll.SetCount(this->signs.Length());
00191     } else { // Change of sign contents.
00192       this->signs.ForceResort();
00193     }
00194 
00195     this->SortSignsList();
00196   }
00197 };
00198 
00199 static const NWidgetPart _nested_sign_list_widgets[] = {
00200   NWidget(NWID_HORIZONTAL),
00201     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00202     NWidget(WWT_CAPTION, COLOUR_GREY, SLW_CAPTION), SetDataTip(STR_SIGN_LIST_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00203     NWidget(WWT_SHADEBOX, COLOUR_GREY),
00204     NWidget(WWT_STICKYBOX, COLOUR_GREY),
00205   EndContainer(),
00206   NWidget(NWID_HORIZONTAL),
00207     NWidget(WWT_PANEL, COLOUR_GREY, SLW_LIST), SetMinimalSize(WD_FRAMETEXT_LEFT + 16 + MAX_LENGTH_SIGN_NAME_PIXELS + WD_FRAMETEXT_RIGHT, 50),
00208               SetResize(1, 10), SetFill(1, 0), EndContainer(),
00209     NWidget(NWID_VERTICAL),
00210       NWidget(WWT_SCROLLBAR, COLOUR_GREY, SLW_SCROLLBAR),
00211       NWidget(WWT_RESIZEBOX, COLOUR_GREY),
00212     EndContainer(),
00213   EndContainer(),
00214 };
00215 
00216 static const WindowDesc _sign_list_desc(
00217   WDP_AUTO, 358, 138,
00218   WC_SIGN_LIST, WC_NONE,
00219   0,
00220   _nested_sign_list_widgets, lengthof(_nested_sign_list_widgets)
00221 );
00222 
00223 
00224 void ShowSignList()
00225 {
00226   AllocateWindowDescFront<SignListWindow>(&_sign_list_desc, 0);
00227 }
00228 
00235 static bool RenameSign(SignID index, const char *text)
00236 {
00237   bool remove = StrEmpty(text);
00238   DoCommandP(0, index, 0, CMD_RENAME_SIGN | (StrEmpty(text) ? CMD_MSG(STR_ERROR_CAN_T_DELETE_SIGN) : CMD_MSG(STR_ERROR_CAN_T_CHANGE_SIGN_NAME)), NULL, text);
00239   return remove;
00240 }
00241 
00243 enum QueryEditSignWidgets {
00244   QUERY_EDIT_SIGN_WIDGET_CAPTION,
00245   QUERY_EDIT_SIGN_WIDGET_TEXT,
00246   QUERY_EDIT_SIGN_WIDGET_OK,
00247   QUERY_EDIT_SIGN_WIDGET_CANCEL,
00248   QUERY_EDIT_SIGN_WIDGET_DELETE,
00249   QUERY_EDIT_SIGN_WIDGET_PREVIOUS,
00250   QUERY_EDIT_SIGN_WIDGET_NEXT,
00251 };
00252 
00253 struct SignWindow : QueryStringBaseWindow, SignList {
00254   SignID cur_sign;
00255 
00256   SignWindow(const WindowDesc *desc, const Sign *si) : QueryStringBaseWindow(MAX_LENGTH_SIGN_NAME_BYTES)
00257   {
00258     this->caption = STR_EDIT_SIGN_CAPTION;
00259     this->afilter = CS_ALPHANUMERAL;
00260 
00261     this->InitNested(desc);
00262 
00263     this->LowerWidget(QUERY_EDIT_SIGN_WIDGET_TEXT);
00264     UpdateSignEditWindow(si);
00265     this->SetFocusedWidget(QUERY_EDIT_SIGN_WIDGET_TEXT);
00266   }
00267 
00268   void UpdateSignEditWindow(const Sign *si)
00269   {
00270     char *last_of = &this->edit_str_buf[this->edit_str_size - 1]; // points to terminating '\0'
00271 
00272     /* Display an empty string when the sign hasnt been edited yet */
00273     if (si->name != NULL) {
00274       SetDParam(0, si->index);
00275       GetString(this->edit_str_buf, STR_SIGN_NAME, last_of);
00276     } else {
00277       GetString(this->edit_str_buf, STR_EMPTY, last_of);
00278     }
00279     *last_of = '\0';
00280 
00281     this->cur_sign = si->index;
00282     InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, MAX_LENGTH_SIGN_NAME_PIXELS);
00283 
00284     this->SetWidgetDirty(QUERY_EDIT_SIGN_WIDGET_TEXT);
00285     this->SetFocusedWidget(QUERY_EDIT_SIGN_WIDGET_TEXT);
00286   }
00287 
00293   const Sign *PrevNextSign(bool next)
00294   {
00295     /* Rebuild the sign list */
00296     this->signs.ForceRebuild();
00297     this->signs.NeedResort();
00298     this->BuildSignsList();
00299     this->SortSignsList();
00300 
00301     /* Search through the list for the current sign, excluding
00302      * - the first sign if we want the previous sign or
00303      * - the last sign if we want the next sign */
00304     uint end = this->signs.Length() - (next ? 1 : 0);
00305     for (uint i = next ? 0 : 1; i < end; i++) {
00306       if (this->cur_sign == this->signs[i]->index) {
00307         /* We've found the current sign, so return the sign before/after it */
00308         return this->signs[i + (next ? 1 : -1)];
00309       }
00310     }
00311     /* If we haven't found the current sign by now, return the last/first sign */
00312     return this->signs[next ? 0 : this->signs.Length() - 1];
00313   }
00314 
00315   virtual void SetStringParameters(int widget) const
00316   {
00317     switch (widget) {
00318       case QUERY_EDIT_SIGN_WIDGET_CAPTION:
00319         SetDParam(0, this->caption);
00320         break;
00321     }
00322   }
00323 
00324   virtual void OnPaint()
00325   {
00326     this->DrawWidgets();
00327     if (!this->IsShaded()) this->DrawEditBox(QUERY_EDIT_SIGN_WIDGET_TEXT);
00328   }
00329 
00330   virtual void OnClick(Point pt, int widget)
00331   {
00332     switch (widget) {
00333       case QUERY_EDIT_SIGN_WIDGET_PREVIOUS:
00334       case QUERY_EDIT_SIGN_WIDGET_NEXT: {
00335         const Sign *si = this->PrevNextSign(widget == QUERY_EDIT_SIGN_WIDGET_NEXT);
00336 
00337         /* Rebuild the sign list */
00338         this->signs.ForceRebuild();
00339         this->signs.NeedResort();
00340         this->BuildSignsList();
00341         this->SortSignsList();
00342 
00343         /* Scroll to sign and reopen window */
00344         ScrollMainWindowToTile(TileVirtXY(si->x, si->y));
00345         UpdateSignEditWindow(si);
00346         break;
00347       }
00348 
00349       case QUERY_EDIT_SIGN_WIDGET_DELETE:
00350         /* Only need to set the buffer to null, the rest is handled as the OK button */
00351         RenameSign(this->cur_sign, "");
00352         /* don't delete this, we are deleted in Sign::~Sign() -> DeleteRenameSignWindow() */
00353         break;
00354 
00355       case QUERY_EDIT_SIGN_WIDGET_OK:
00356         if (RenameSign(this->cur_sign, this->text.buf)) break;
00357         /* FALL THROUGH */
00358 
00359       case QUERY_EDIT_SIGN_WIDGET_CANCEL:
00360         delete this;
00361         break;
00362     }
00363   }
00364 
00365   virtual EventState OnKeyPress(uint16 key, uint16 keycode)
00366   {
00367     EventState state = ES_NOT_HANDLED;
00368     switch (this->HandleEditBoxKey(QUERY_EDIT_SIGN_WIDGET_TEXT, key, keycode, state)) {
00369       default: break;
00370 
00371       case HEBR_CONFIRM:
00372         if (RenameSign(this->cur_sign, this->text.buf)) break;
00373         /* FALL THROUGH */
00374 
00375       case HEBR_CANCEL: // close window, abandon changes
00376         delete this;
00377         break;
00378     }
00379     return state;
00380   }
00381 
00382   virtual void OnMouseLoop()
00383   {
00384     this->HandleEditBox(QUERY_EDIT_SIGN_WIDGET_TEXT);
00385   }
00386 
00387   virtual void OnOpenOSKWindow(int wid)
00388   {
00389     ShowOnScreenKeyboard(this, wid, QUERY_EDIT_SIGN_WIDGET_CANCEL, QUERY_EDIT_SIGN_WIDGET_OK);
00390   }
00391 };
00392 
00393 static const NWidgetPart _nested_query_sign_edit_widgets[] = {
00394   NWidget(NWID_HORIZONTAL),
00395     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00396     NWidget(WWT_CAPTION, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_CAPTION), SetDataTip(STR_WHITE_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00397   EndContainer(),
00398   NWidget(WWT_PANEL, COLOUR_GREY),
00399     NWidget(WWT_EDITBOX, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_TEXT), SetMinimalSize(256, 12), SetDataTip(STR_EDIT_SIGN_SIGN_OSKTITLE, STR_NULL), SetPadding(2, 2, 2, 2),
00400   EndContainer(),
00401   NWidget(NWID_HORIZONTAL),
00402     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_OK), SetMinimalSize(61, 12), SetDataTip(STR_BUTTON_OK, STR_NULL),
00403     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_CANCEL), SetMinimalSize(60, 12), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
00404     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_DELETE), SetMinimalSize(60, 12), SetDataTip(STR_TOWN_VIEW_DELETE_BUTTON, STR_NULL),
00405     NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), EndContainer(),
00406     NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_PREVIOUS), SetMinimalSize(11, 12), SetDataTip(AWV_DECREASE, STR_EDIT_SIGN_PREVIOUS_SIGN_TOOLTIP),
00407     NWidget(NWID_BUTTON_ARROW, COLOUR_GREY, QUERY_EDIT_SIGN_WIDGET_NEXT), SetMinimalSize(11, 12), SetDataTip(AWV_INCREASE, STR_EDIT_SIGN_NEXT_SIGN_TOOLTIP),
00408   EndContainer(),
00409 };
00410 
00411 static const WindowDesc _query_sign_edit_desc(
00412   WDP_AUTO, 0, 0,
00413   WC_QUERY_STRING, WC_NONE,
00414   WDF_CONSTRUCTION | WDF_UNCLICK_BUTTONS,
00415   _nested_query_sign_edit_widgets, lengthof(_nested_query_sign_edit_widgets)
00416 );
00417 
00418 void HandleClickOnSign(const Sign *si)
00419 {
00420   if (_ctrl_pressed && si->owner == _local_company) {
00421     RenameSign(si->index, NULL);
00422     return;
00423   }
00424   ShowRenameSignWindow(si);
00425 }
00426 
00427 void ShowRenameSignWindow(const Sign *si)
00428 {
00429   /* Delete all other edit windows */
00430   DeleteWindowById(WC_QUERY_STRING, 0);
00431 
00432   new SignWindow(&_query_sign_edit_desc, si);
00433 }
00434 
00435 void DeleteRenameSignWindow(SignID sign)
00436 {
00437   SignWindow *w = dynamic_cast<SignWindow *>(FindWindowById(WC_QUERY_STRING, 0));
00438 
00439   if (w != NULL && w->cur_sign == sign) delete w;
00440 }

Generated on Tue Jan 5 21:02:58 2010 for OpenTTD by  doxygen 1.5.6