misc_gui.cpp

Go to the documentation of this file.
00001 /* $Id: misc_gui.cpp 18606 2009-12-22 20:43:25Z 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 "openttd.h"
00014 #include "landscape.h"
00015 #include "newgrf_text.h"
00016 #include "saveload/saveload.h"
00017 #include "gui.h"
00018 #include "station_gui.h"
00019 #include "viewport_func.h"
00020 #include "gfx_func.h"
00021 #include "station_func.h"
00022 #include "command_func.h"
00023 #include "company_func.h"
00024 #include "town.h"
00025 #include "network/network.h"
00026 #include "network/network_content.h"
00027 #include "company_base.h"
00028 #include "texteff.hpp"
00029 #include "cargotype.h"
00030 #include "company_manager_face.h"
00031 #include "strings_func.h"
00032 #include "fileio_func.h"
00033 #include "fios.h"
00034 #include "zoom_func.h"
00035 #include "window_func.h"
00036 #include "tilehighlight_func.h"
00037 #include "querystring_gui.h"
00038 
00039 #include "table/strings.h"
00040 
00041 
00048 bool GetClipboardContents(char *buffer, size_t buff_len);
00049 
00050 
00051 /* Variables to display file lists */
00052 SaveLoadDialogMode _saveload_mode;
00053 
00054 
00055 static bool _fios_path_changed;
00056 static bool _savegame_sort_dirty;
00057 int _caret_timer;
00058 
00060 enum LandInfoWidgets {
00061   LIW_BACKGROUND, 
00062 };
00063 
00064 static const NWidgetPart _nested_land_info_widgets[] = {
00065   NWidget(NWID_HORIZONTAL),
00066     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00067     NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_LAND_AREA_INFORMATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00068   EndContainer(),
00069   NWidget(WWT_PANEL, COLOUR_GREY, LIW_BACKGROUND), EndContainer(),
00070 };
00071 
00072 static const WindowDesc _land_info_desc(
00073   WDP_AUTO, 0, 0,
00074   WC_LAND_INFO, WC_NONE,
00075   0,
00076   _nested_land_info_widgets, lengthof(_nested_land_info_widgets)
00077 );
00078 
00079 class LandInfoWindow : public Window {
00080   enum {
00081     LAND_INFO_CENTERED_LINES   = 12,                       
00082     LAND_INFO_MULTICENTER_LINE = LAND_INFO_CENTERED_LINES, 
00083     LAND_INFO_LINE_END,
00084 
00085     LAND_INFO_LINE_BUFF_SIZE = 512,
00086   };
00087 
00088 public:
00089   char landinfo_data[LAND_INFO_LINE_END][LAND_INFO_LINE_BUFF_SIZE];
00090 
00091   virtual void OnPaint()
00092   {
00093     this->DrawWidgets();
00094   }
00095 
00096   virtual void DrawWidget(const Rect &r, int widget) const
00097   {
00098     if (widget != LIW_BACKGROUND) return;
00099 
00100     uint y = r.top + WD_TEXTPANEL_TOP;
00101     for (uint i = 0; i < LAND_INFO_CENTERED_LINES; i++) {
00102       if (StrEmpty(this->landinfo_data[i])) break;
00103 
00104       DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, this->landinfo_data[i], i == 0 ? TC_LIGHT_BLUE : TC_FROMSTRING, SA_CENTER);
00105       y += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
00106       if (i == 0) y += 4;
00107     }
00108 
00109     if (!StrEmpty(this->landinfo_data[LAND_INFO_MULTICENTER_LINE])) {
00110       SetDParamStr(0, this->landinfo_data[LAND_INFO_MULTICENTER_LINE]);
00111       DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, r.bottom - WD_TEXTPANEL_BOTTOM, STR_JUST_RAW_STRING, TC_FROMSTRING, SA_CENTER);
00112     }
00113   }
00114 
00115   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
00116   {
00117     if (widget != LIW_BACKGROUND) return;
00118 
00119     size->height = WD_TEXTPANEL_TOP + WD_TEXTPANEL_BOTTOM;
00120     for (uint i = 0; i < LAND_INFO_CENTERED_LINES; i++) {
00121       if (StrEmpty(this->landinfo_data[i])) break;
00122 
00123       uint width = GetStringBoundingBox(this->landinfo_data[i]).width + WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT;
00124       size->width = max(size->width, width);
00125 
00126       size->height += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
00127       if (i == 0) size->height += 4;
00128     }
00129 
00130     if (!StrEmpty(this->landinfo_data[LAND_INFO_MULTICENTER_LINE])) {
00131       uint width = GetStringBoundingBox(this->landinfo_data[LAND_INFO_MULTICENTER_LINE]).width + WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT;
00132       size->width = max(size->width, min(300u, width));
00133       SetDParamStr(0, this->landinfo_data[LAND_INFO_MULTICENTER_LINE]);
00134       size->height += GetStringHeight(STR_JUST_RAW_STRING, size->width - WD_FRAMETEXT_LEFT - WD_FRAMETEXT_RIGHT);
00135     }
00136   }
00137 
00138   LandInfoWindow(TileIndex tile) : Window() {
00139     Town *t = ClosestTownFromTile(tile, _settings_game.economy.dist_local_authority);
00140 
00141     /* Because build_date is not set yet in every TileDesc, we make sure it is empty */
00142     TileDesc td;
00143 
00144     td.build_date = INVALID_DATE;
00145 
00146     /* Most tiles have only one owner, but
00147      *  - drivethrough roadstops can be build on town owned roads (up to 2 owners) and
00148      *  - roads can have up to four owners (railroad, road, tram, 3rd-roadtype "highway").
00149      */
00150     td.owner_type[0] = STR_LAND_AREA_INFORMATION_OWNER; // At least one owner is displayed, though it might be "N/A".
00151     td.owner_type[1] = STR_NULL;       // STR_NULL results in skipping the owner
00152     td.owner_type[2] = STR_NULL;
00153     td.owner_type[3] = STR_NULL;
00154     td.owner[0] = OWNER_NONE;
00155     td.owner[1] = OWNER_NONE;
00156     td.owner[2] = OWNER_NONE;
00157     td.owner[3] = OWNER_NONE;
00158 
00159     td.station_class = STR_NULL;
00160     td.station_name = STR_NULL;
00161 
00162     td.grf = NULL;
00163 
00164     CargoArray acceptance;
00165     AddAcceptedCargo(tile, acceptance, NULL);
00166     GetTileDesc(tile, &td);
00167 
00168     uint line_nr = 0;
00169 
00170     /* Tiletype */
00171     SetDParam(0, td.dparam[0]);
00172     GetString(this->landinfo_data[line_nr], td.str, lastof(this->landinfo_data[line_nr]));
00173     line_nr++;
00174 
00175     /* Up to four owners */
00176     for (uint i = 0; i < 4; i++) {
00177       if (td.owner_type[i] == STR_NULL) continue;
00178 
00179       SetDParam(0, STR_LAND_AREA_INFORMATION_OWNER_N_A);
00180       if (td.owner[i] != OWNER_NONE && td.owner[i] != OWNER_WATER) GetNameOfOwner(td.owner[i], tile);
00181       GetString(this->landinfo_data[line_nr], td.owner_type[i], lastof(this->landinfo_data[line_nr]));
00182       line_nr++;
00183     }
00184 
00185     /* Cost to clear/revenue when cleared */
00186     StringID str = STR_LAND_AREA_INFORMATION_COST_TO_CLEAR_N_A;
00187     Company *c = Company::GetIfValid(_local_company);
00188     if (c != NULL) {
00189       Money old_money = c->money;
00190       c->money = INT64_MAX;
00191       CommandCost costclear = DoCommand(tile, 0, 0, DC_NONE, CMD_LANDSCAPE_CLEAR);
00192       c->money = old_money;
00193       if (CmdSucceeded(costclear)) {
00194         Money cost = costclear.GetCost();
00195         if (cost < 0) {
00196           cost = -cost; // Negate negative cost to a positive revenue
00197           str = STR_LAND_AREA_INFORMATION_REVENUE_WHEN_CLEARED;
00198         } else {
00199           str = STR_LAND_AREA_INFORMATION_COST_TO_CLEAR;
00200         }
00201         SetDParam(0, cost);
00202       }
00203     }
00204     GetString(this->landinfo_data[line_nr], str, lastof(this->landinfo_data[line_nr]));
00205     line_nr++;
00206 
00207     /* Location */
00208     char tmp[16];
00209     snprintf(tmp, lengthof(tmp), "0x%.4X", tile);
00210     SetDParam(0, TileX(tile));
00211     SetDParam(1, TileY(tile));
00212     SetDParam(2, TileHeight(tile));
00213     SetDParamStr(3, tmp);
00214     GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_LANDINFO_COORDS, lastof(this->landinfo_data[line_nr]));
00215     line_nr++;
00216 
00217     /* Local authority */
00218     SetDParam(0, STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE);
00219     if (t != NULL) {
00220       SetDParam(0, STR_TOWN_NAME);
00221       SetDParam(1, t->index);
00222     }
00223     GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY, lastof(this->landinfo_data[line_nr]));
00224     line_nr++;
00225 
00226     /* Build date */
00227     if (td.build_date != INVALID_DATE) {
00228       SetDParam(0, td.build_date);
00229       GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_BUILD_DATE, lastof(this->landinfo_data[line_nr]));
00230       line_nr++;
00231     }
00232 
00233     /* Station class */
00234     if (td.station_class != STR_NULL) {
00235       SetDParam(0, td.station_class);
00236       GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_STATION_CLASS, lastof(this->landinfo_data[line_nr]));
00237       line_nr++;
00238     }
00239 
00240     /* Station type name */
00241     if (td.station_name != STR_NULL) {
00242       SetDParam(0, td.station_name);
00243       GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_STATION_TYPE, lastof(this->landinfo_data[line_nr]));
00244       line_nr++;
00245     }
00246 
00247     /* NewGRF name */
00248     if (td.grf != NULL) {
00249       SetDParamStr(0, td.grf);
00250       GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_NEWGRF_NAME, lastof(this->landinfo_data[line_nr]));
00251       line_nr++;
00252     }
00253 
00254     assert(line_nr < LAND_INFO_CENTERED_LINES);
00255 
00256     /* Mark last line empty */
00257     this->landinfo_data[line_nr][0] = '\0';
00258 
00259     /* Cargo acceptance is displayed in a extra multiline */
00260     char *strp = GetString(this->landinfo_data[LAND_INFO_MULTICENTER_LINE], STR_LAND_AREA_INFORMATION_CARGO_ACCEPTED, lastof(this->landinfo_data[LAND_INFO_MULTICENTER_LINE]));
00261     bool found = false;
00262 
00263     for (CargoID i = 0; i < NUM_CARGO; ++i) {
00264       if (acceptance[i] > 0) {
00265         /* Add a comma between each item. */
00266         if (found) {
00267           *strp++ = ',';
00268           *strp++ = ' ';
00269         }
00270         found = true;
00271 
00272         /* If the accepted value is less than 8, show it in 1/8:ths */
00273         if (acceptance[i] < 8) {
00274           SetDParam(0, acceptance[i]);
00275           SetDParam(1, CargoSpec::Get(i)->name);
00276           strp = GetString(strp, STR_LAND_AREA_INFORMATION_CARGO_EIGHTS, lastof(this->landinfo_data[LAND_INFO_MULTICENTER_LINE]));
00277         } else {
00278           strp = GetString(strp, CargoSpec::Get(i)->name, lastof(this->landinfo_data[LAND_INFO_MULTICENTER_LINE]));
00279         }
00280       }
00281     }
00282     if (!found) this->landinfo_data[LAND_INFO_MULTICENTER_LINE][0] = '\0';
00283 
00284     this->InitNested(&_land_info_desc);
00285 
00286 #if defined(_DEBUG)
00287 # define LANDINFOD_LEVEL 0
00288 #else
00289 # define LANDINFOD_LEVEL 1
00290 #endif
00291     DEBUG(misc, LANDINFOD_LEVEL, "TILE: %#x (%i,%i)", tile, TileX(tile), TileY(tile));
00292     DEBUG(misc, LANDINFOD_LEVEL, "type_height  = %#x", _m[tile].type_height);
00293     DEBUG(misc, LANDINFOD_LEVEL, "m1           = %#x", _m[tile].m1);
00294     DEBUG(misc, LANDINFOD_LEVEL, "m2           = %#x", _m[tile].m2);
00295     DEBUG(misc, LANDINFOD_LEVEL, "m3           = %#x", _m[tile].m3);
00296     DEBUG(misc, LANDINFOD_LEVEL, "m4           = %#x", _m[tile].m4);
00297     DEBUG(misc, LANDINFOD_LEVEL, "m5           = %#x", _m[tile].m5);
00298     DEBUG(misc, LANDINFOD_LEVEL, "m6           = %#x", _m[tile].m6);
00299     DEBUG(misc, LANDINFOD_LEVEL, "m7           = %#x", _me[tile].m7);
00300 #undef LANDINFOD_LEVEL
00301   }
00302 };
00303 
00304 static void Place_LandInfo(TileIndex tile)
00305 {
00306   DeleteWindowById(WC_LAND_INFO, 0);
00307   new LandInfoWindow(tile);
00308 }
00309 
00310 void PlaceLandBlockInfo()
00311 {
00312   if (_cursor.sprite == SPR_CURSOR_QUERY) {
00313     ResetObjectToPlace();
00314   } else {
00315     _place_proc = Place_LandInfo;
00316     SetObjectToPlace(SPR_CURSOR_QUERY, PAL_NONE, HT_RECT, WC_MAIN_TOOLBAR, 0);
00317   }
00318 }
00319 
00321 enum AboutWidgets {
00322   AW_SCROLLING_TEXT,       
00323   AW_WEBSITE,              
00324 };
00325 
00326 static const NWidgetPart _nested_about_widgets[] = {
00327   NWidget(NWID_HORIZONTAL),
00328     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00329     NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_ABOUT_OPENTTD, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00330   EndContainer(),
00331   NWidget(WWT_PANEL, COLOUR_GREY), SetPIP(4, 2, 4),
00332     NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_ABOUT_ORIGINAL_COPYRIGHT, STR_NULL),
00333     NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_ABOUT_VERSION, STR_NULL),
00334     NWidget(WWT_FRAME, COLOUR_GREY), SetPadding(0, 5, 1, 5),
00335       NWidget(WWT_EMPTY, INVALID_COLOUR, AW_SCROLLING_TEXT),
00336     EndContainer(),
00337     NWidget(WWT_LABEL, COLOUR_GREY, AW_WEBSITE), SetDataTip(STR_BLACK_RAW_STRING, STR_NULL),
00338     NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_ABOUT_COPYRIGHT_OPENTTD, STR_NULL),
00339   EndContainer(),
00340 };
00341 
00342 static const WindowDesc _about_desc(
00343   WDP_CENTER, 0, 0,
00344   WC_GAME_OPTIONS, WC_NONE,
00345   0,
00346   _nested_about_widgets, lengthof(_nested_about_widgets)
00347 );
00348 
00349 static const char * const _credits[] = {
00350   "Original design by Chris Sawyer",
00351   "Original graphics by Simon Foster",
00352   "",
00353   "The OpenTTD team (in alphabetical order):",
00354   "  Albert Hofkamp (Alberth) - GUI expert",
00355   "  Jean-Francois Claeys (Belugas) - GUI, newindustries and more",
00356   "  Bjarni Corfitzen (Bjarni) - MacOSX port, coder and vehicles",
00357   "  Matthijs Kooijman (blathijs) - Pathfinder-guru, pool rework",
00358   "  Victor Fischer (Celestar) - Programming everywhere you need him to",
00359   "  Christoph Elsenhans (frosch) - General coding",
00360   "  Lo\xC3\xAF""c Guilloux (glx) - Windows Expert",
00361   "  Michael Lutz (michi_cc) - Path based signals",
00362   "  Owen Rudge (orudge) - Forum host, OS/2 port",
00363   "  Peter Nelson (peter1138) - Spiritual descendant from newGRF gods",
00364   "  Remko Bijker (Rubidium) - Lead coder and way more",
00365   "  Zden\xC4\x9Bk Sojka (SmatZ) - Bug finder and fixer",
00366   "  Thijs Marinussen (Yexo) - AI Framework",
00367   "",
00368   "Inactive Developers:",
00369   "  Tam\xC3\xA1s Farag\xC3\xB3 (Darkvater) - Ex-Lead coder",
00370   "  Jaroslav Mazanec (KUDr) - YAPG (Yet Another Pathfinder God) ;)",
00371   "  Jonathan Coome (Maedhros) - High priest of the NewGRF Temple",
00372   "  Attila B\xC3\xA1n (MiHaMiX) - Developer WebTranslator 1 and 2",
00373   "  Christoph Mallon (Tron) - Programmer, code correctness police",
00374   "",
00375   "Retired Developers:",
00376   "  Ludvig Strigeus (ludde) - OpenTTD author, main coder (0.1 - 0.3.3)",
00377   "  Serge Paquet (vurlix) - Assistant project manager, coder (0.1 - 0.3.3)",
00378   "  Dominik Scherer (dominik81) - Lead programmer, GUI expert (0.3.0 - 0.3.6)",
00379   "  Benedikt Br\xC3\xBCggemeier (skidd13) - Bug fixer and code reworker",
00380   "  Patric Stout (TrueLight) - Programmer (0.3 - pre0.7), sys op (active)",
00381   "",
00382   "Special thanks go out to:",
00383   "  Josef Drexler - For his great work on TTDPatch",
00384   "  Marcin Grzegorczyk - For his documentation of TTD internals",
00385   "  Petr Baudi\xC5\xA1 (pasky) - Many patches, newGRF support",
00386   "  Stefan Mei\xC3\x9Fner (sign_de) - For his work on the console",
00387   "  Simon Sasburg (HackyKid) - Many bugfixes he has blessed us with",
00388   "  Cian Duffy (MYOB) - BeOS port / manual writing",
00389   "  Christian Rosentreter (tokai) - MorphOS / AmigaOS port",
00390   "  Richard Kempton (richK) - additional airports, initial TGP implementation",
00391   "",
00392   "  Alberto Demichelis - Squirrel scripting language \xC2\xA9 2003-2008",
00393   "  Markus F.X.J. Oberhumer - (Mini)LZO for loading old savegames \xC2\xA9 1996-2008",
00394   "  L. Peter Deutsch - MD5 implementation \xC2\xA9 1999, 2000, 2002",
00395   "  Michael Blunck - Pre-Signals and Semaphores \xC2\xA9 2003",
00396   "  George - Canal/Lock graphics \xC2\xA9 2003-2004",
00397   "  David Dallaston - Tram tracks",
00398   "  Marcin Grzegorczyk - Foundations for Tracks on Slopes",
00399   "  All Translators - Who made OpenTTD a truly international game",
00400   "  Bug Reporters - Without whom OpenTTD would still be full of bugs!",
00401   "",
00402   "",
00403   "And last but not least:",
00404   "  Chris Sawyer - For an amazing game!"
00405 };
00406 
00407 struct AboutWindow : public Window {
00408   int text_position;                       
00409   byte counter;                            
00410   int line_height;                         
00411   static const int num_visible_lines = 19; 
00412 
00413   AboutWindow() : Window()
00414   {
00415     this->InitNested(&_about_desc);
00416 
00417     this->counter = 5;
00418     this->text_position = this->GetWidget<NWidgetBase>(AW_SCROLLING_TEXT)->pos_y + this->GetWidget<NWidgetBase>(AW_SCROLLING_TEXT)->current_y;
00419   }
00420 
00421   virtual void SetStringParameters(int widget) const
00422   {
00423     if (widget == AW_WEBSITE) SetDParamStr(0, "Website: http://www.openttd.org");
00424   }
00425 
00426   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
00427   {
00428     if (widget != AW_SCROLLING_TEXT) return;
00429 
00430     this->line_height = FONT_HEIGHT_NORMAL;
00431 
00432     Dimension d;
00433     d.height = this->line_height * num_visible_lines;
00434 
00435     d.width = 0;
00436     for (uint i = 0; i < lengthof(_credits); i++) {
00437       d.width = max(d.width, GetStringBoundingBox(_credits[i]).width);
00438     }
00439     *size = maxdim(*size, d);
00440   }
00441 
00442   virtual void OnPaint()
00443   {
00444     this->DrawWidgets();
00445   }
00446 
00447   virtual void DrawWidget(const Rect &r, int widget) const
00448   {
00449     if (widget != AW_SCROLLING_TEXT) return;
00450 
00451     int y = this->text_position;
00452 
00453     /* Show all scrolling _credits */
00454     for (uint i = 0; i < lengthof(_credits); i++) {
00455       if (y >= r.top + 7 && y < r.bottom - this->line_height) {
00456         DrawString(r.left, r.right, y, _credits[i], TC_BLACK, SA_LEFT | SA_FORCE);
00457       }
00458       y += this->line_height;
00459     }
00460   }
00461 
00462   virtual void OnTick()
00463   {
00464     if (--this->counter == 0) {
00465       this->counter = 5;
00466       this->text_position--;
00467       /* If the last text has scrolled start a new from the start */
00468       if (this->text_position < (int)(this->GetWidget<NWidgetBase>(AW_SCROLLING_TEXT)->pos_y - lengthof(_credits) * this->line_height)) {
00469         this->text_position = this->GetWidget<NWidgetBase>(AW_SCROLLING_TEXT)->pos_y + this->GetWidget<NWidgetBase>(AW_SCROLLING_TEXT)->current_y;
00470       }
00471       this->SetDirty();
00472     }
00473   }
00474 };
00475 
00476 void ShowAboutWindow()
00477 {
00478   DeleteWindowById(WC_GAME_OPTIONS, 0);
00479   new AboutWindow();
00480 }
00481 
00483 enum ErrorMessageWidgets {
00484   EMW_CAPTION,
00485   EMW_FACE,
00486   EMW_MESSAGE,
00487 };
00488 
00489 static const NWidgetPart _nested_errmsg_widgets[] = {
00490   NWidget(NWID_HORIZONTAL),
00491     NWidget(WWT_CLOSEBOX, COLOUR_RED),
00492     NWidget(WWT_CAPTION, COLOUR_RED, EMW_CAPTION), SetDataTip(STR_ERROR_MESSAGE_CAPTION, STR_NULL),
00493   EndContainer(),
00494   NWidget(WWT_PANEL, COLOUR_RED),
00495     NWidget(WWT_EMPTY, COLOUR_RED, EMW_MESSAGE), SetPadding(0, 2, 0, 2), SetMinimalSize(236, 32),
00496   EndContainer(),
00497 };
00498 
00499 static const WindowDesc _errmsg_desc(
00500   WDP_MANUAL, 0, 0,
00501   WC_ERRMSG, WC_NONE,
00502   0,
00503   _nested_errmsg_widgets, lengthof(_nested_errmsg_widgets)
00504 );
00505 
00506 static const NWidgetPart _nested_errmsg_face_widgets[] = {
00507   NWidget(NWID_HORIZONTAL),
00508     NWidget(WWT_CLOSEBOX, COLOUR_RED),
00509     NWidget(WWT_CAPTION, COLOUR_RED, EMW_CAPTION), SetDataTip(STR_ERROR_MESSAGE_CAPTION_OTHER_COMPANY, STR_NULL),
00510   EndContainer(),
00511   NWidget(WWT_PANEL, COLOUR_RED),
00512     NWidget(NWID_HORIZONTAL), SetPIP(2, 1, 2),
00513       NWidget(WWT_EMPTY, COLOUR_RED, EMW_FACE), SetMinimalSize(91, 120), SetFill(0, 1), SetPadding(2, 0, 1, 0),
00514       NWidget(WWT_EMPTY, COLOUR_RED, EMW_MESSAGE), SetFill(0, 1), SetMinimalSize(238, 123),
00515     EndContainer(),
00516   EndContainer(),
00517 };
00518 
00519 static const WindowDesc _errmsg_face_desc(
00520   WDP_MANUAL, 0, 0,
00521   WC_ERRMSG, WC_NONE,
00522   0,
00523   _nested_errmsg_face_widgets, lengthof(_nested_errmsg_face_widgets)
00524 );
00525 
00527 struct ErrmsgWindow : public Window {
00528 private:
00529   uint duration;                  
00530   uint64 decode_params[20];       
00531   StringID summary_msg;           
00532   StringID detailed_msg;          
00533   uint height_summary;            
00534   uint height_detailed;           
00535   Point position;                 
00536 
00537 public:
00538   ErrmsgWindow(Point pt, const WindowDesc *desc, StringID summary_msg, StringID detailed_msg, bool no_timeout) : Window()
00539   {
00540     this->position = pt;
00541     this->duration = no_timeout ? 0 : _settings_client.gui.errmsg_duration;
00542     CopyOutDParam(this->decode_params, 0, lengthof(this->decode_params));
00543     this->summary_msg  = summary_msg;
00544     this->detailed_msg = detailed_msg;
00545 
00546     assert(summary_msg != INVALID_STRING_ID);
00547 
00548     this->InitNested(desc);
00549   }
00550 
00551   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
00552   {
00553     if (widget != EMW_MESSAGE) return;
00554 
00555     CopyInDParam(0, this->decode_params, lengthof(this->decode_params));
00556     /* If the error message comes from a NewGRF, we must use the text ref. stack reserved for error messages.
00557      * If the message doesn't come from a NewGRF, it won't use the TTDP-style text ref. stack, so we won't hurt anything
00558      */
00559     SwitchToErrorRefStack();
00560     RewindTextRefStack();
00561 
00562     int text_width = max(0, (int)size->width - WD_FRAMETEXT_LEFT - WD_FRAMETEXT_RIGHT);
00563     this->height_summary  = GetStringHeight(this->summary_msg, text_width);
00564     this->height_detailed = (this->detailed_msg == INVALID_STRING_ID) ? 0 : GetStringHeight(this->detailed_msg, text_width);
00565 
00566     SwitchToNormalRefStack(); // Switch back to the normal text ref. stack for NewGRF texts.
00567 
00568     uint panel_height = WD_FRAMERECT_TOP + this->height_summary + WD_FRAMERECT_BOTTOM;
00569     if (this->detailed_msg != INVALID_STRING_ID) panel_height += this->height_detailed + WD_PAR_VSEP_WIDE;
00570 
00571     size->height = max(size->height, panel_height);
00572   }
00573 
00574   virtual Point OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
00575   {
00576     /* Position (0, 0) given, center the window. */
00577     if (this->position.x == 0 && this->position.y == 0) {
00578       Point pt = {(_screen.width - sm_width) >> 1, (_screen.height - sm_height) >> 1};
00579       return pt;
00580     }
00581 
00582     /* Find the free screen space between the main toolbar at the top, and the statusbar at the bottom.
00583      * Add a fixed distance 20 to make it less cluttered.
00584      */
00585     int scr_top = GetMainViewTop() + 20;
00586     int scr_bot = GetMainViewBottom() - 20;
00587 
00588     Point pt = RemapCoords2(this->position.x, this->position.y);
00589     const ViewPort *vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport;
00590     if (this->detailed_msg != STR_ERROR_OWNED_BY || GetDParamX(this->decode_params, 2) >= MAX_COMPANIES) {
00591       /* move x pos to opposite corner */
00592       pt.x = UnScaleByZoom(pt.x - vp->virtual_left, vp->zoom) + vp->left;
00593       pt.x = (pt.x < (_screen.width >> 1)) ? _screen.width - sm_width - 20 : 20; // Stay 20 pixels away from the edge of the screen.
00594 
00595       /* move y pos to opposite corner */
00596       pt.y = UnScaleByZoom(pt.y - vp->virtual_top, vp->zoom) + vp->top;
00597       pt.y = (pt.y < (_screen.height >> 1)) ? scr_bot - sm_height : scr_top;
00598     } else {
00599       pt.x = Clamp(UnScaleByZoom(pt.x - vp->virtual_left, vp->zoom) + vp->left - (sm_width / 2),  0, _screen.width  - sm_width);
00600       pt.y = Clamp(UnScaleByZoom(pt.y - vp->virtual_top,  vp->zoom) + vp->top  - (sm_height / 2), scr_top, scr_bot - sm_height);
00601     }
00602     return pt;
00603   }
00604 
00605   virtual void OnPaint()
00606   {
00607     this->DrawWidgets();
00608   }
00609 
00610   virtual void SetStringParameters(int widget) const
00611   {
00612     if (widget == EMW_CAPTION) CopyInDParam(0, this->decode_params, lengthof(this->decode_params));
00613   }
00614 
00615   virtual void DrawWidget(const Rect &r, int widget) const
00616   {
00617     switch (widget) {
00618       case EMW_FACE: {
00619         const Company *c = Company::Get((CompanyID)GetDParamX(this->decode_params, 2));
00620         DrawCompanyManagerFace(c->face, c->colour, r.left, r.top);
00621         break;
00622       }
00623 
00624       case EMW_MESSAGE:
00625         CopyInDParam(0, this->decode_params, lengthof(this->decode_params));
00626         SwitchToErrorRefStack();
00627         RewindTextRefStack();
00628 
00629         if (this->detailed_msg == INVALID_STRING_ID) {
00630           DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM,
00631               this->summary_msg, TC_FROMSTRING, SA_CENTER);
00632         } else {
00633           int extra = (r.bottom - r.top + 1 - this->height_summary - this->height_detailed - WD_PAR_VSEP_WIDE) / 2;
00634 
00635           int top = r.top + WD_FRAMERECT_TOP;
00636           int bottom = top + this->height_summary + extra;
00637           DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, top, bottom, this->summary_msg, TC_FROMSTRING, SA_CENTER);
00638 
00639           bottom = r.bottom - WD_FRAMERECT_BOTTOM;
00640           top = bottom - this->height_detailed - extra;
00641           DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, top, bottom, this->detailed_msg, TC_FROMSTRING, SA_CENTER);
00642         }
00643 
00644         SwitchToNormalRefStack(); // Switch back to the normal text ref. stack for NewGRF texts.
00645         break;
00646 
00647       default:
00648         break;
00649     }
00650   }
00651 
00652   virtual void OnMouseLoop()
00653   {
00654     /* Disallow closing the window too easily, if timeout is disabled */
00655     if (_right_button_down && this->duration != 0) delete this;
00656   }
00657 
00658   virtual void OnHundredthTick()
00659   {
00660     /* Timeout enabled? */
00661     if (this->duration != 0) {
00662       this->duration--;
00663       if (this->duration == 0) delete this;
00664     }
00665   }
00666 
00667   ~ErrmsgWindow()
00668   {
00669     SetRedErrorSquare(INVALID_TILE);
00670     extern StringID _switch_mode_errorstr;
00671     _switch_mode_errorstr = INVALID_STRING_ID;
00672   }
00673 
00674   virtual EventState OnKeyPress(uint16 key, uint16 keycode)
00675   {
00676     if (keycode != WKC_SPACE) return ES_NOT_HANDLED;
00677     delete this;
00678     return ES_HANDLED;
00679   }
00680 };
00681 
00690 void ShowErrorMessage(StringID summary_msg, StringID detailed_msg, int x, int y, bool no_timeout)
00691 {
00692   DeleteWindowById(WC_ERRMSG, 0);
00693 
00694   if (_settings_client.gui.errmsg_duration == 0 && !no_timeout) return;
00695 
00696   if (summary_msg == STR_NULL) summary_msg = STR_EMPTY;
00697 
00698   Point pt = {x, y};
00699   const WindowDesc *desc = (detailed_msg != STR_ERROR_OWNED_BY || GetDParam(2) >= MAX_COMPANIES) ? &_errmsg_desc : &_errmsg_face_desc;
00700   new ErrmsgWindow(pt, desc, summary_msg, detailed_msg, no_timeout);
00701 }
00702 
00703 void ShowEstimatedCostOrIncome(Money cost, int x, int y)
00704 {
00705   StringID msg = STR_MESSAGE_ESTIMATED_COST;
00706 
00707   if (cost < 0) {
00708     cost = -cost;
00709     msg = STR_MESSAGE_ESTIMATED_INCOME;
00710   }
00711   SetDParam(0, cost);
00712   ShowErrorMessage(msg, INVALID_STRING_ID, x, y);
00713 }
00714 
00715 void ShowCostOrIncomeAnimation(int x, int y, int z, Money cost)
00716 {
00717   Point pt = RemapCoords(x, y, z);
00718   StringID msg = STR_INCOME_FLOAT_COST;
00719 
00720   if (cost < 0) {
00721     cost = -cost;
00722     msg = STR_INCOME_FLOAT_INCOME;
00723   }
00724   SetDParam(0, cost);
00725   AddTextEffect(msg, pt.x, pt.y, 0x250, TE_RISING);
00726 }
00727 
00728 void ShowFeederIncomeAnimation(int x, int y, int z, Money cost)
00729 {
00730   Point pt = RemapCoords(x, y, z);
00731 
00732   SetDParam(0, cost);
00733   AddTextEffect(STR_FEEDER, pt.x, pt.y, 0x250, TE_RISING);
00734 }
00735 
00736 TextEffectID ShowFillingPercent(int x, int y, int z, uint8 percent, StringID string)
00737 {
00738   Point pt = RemapCoords(x, y, z);
00739 
00740   assert(string != STR_NULL);
00741 
00742   SetDParam(0, percent);
00743   return AddTextEffect(string, pt.x, pt.y, 0xFFFF, TE_STATIC);
00744 }
00745 
00746 void UpdateFillingPercent(TextEffectID te_id, uint8 percent, StringID string)
00747 {
00748   assert(string != STR_NULL);
00749 
00750   SetDParam(0, percent);
00751   UpdateTextEffect(te_id, string);
00752 }
00753 
00754 void HideFillingPercent(TextEffectID *te_id)
00755 {
00756   if (*te_id == INVALID_TE_ID) return;
00757 
00758   RemoveTextEffect(*te_id);
00759   *te_id = INVALID_TE_ID;
00760 }
00761 
00762 static const NWidgetPart _nested_tooltips_widgets[] = {
00763   NWidget(WWT_PANEL, COLOUR_GREY, 0), SetMinimalSize(200, 32), EndContainer(),
00764 };
00765 
00766 static const WindowDesc _tool_tips_desc(
00767   WDP_MANUAL, 0, 0, // Coordinates and sizes are not used,
00768   WC_TOOLTIPS, WC_NONE,
00769   0,
00770   _nested_tooltips_widgets, lengthof(_nested_tooltips_widgets)
00771 );
00772 
00774 struct TooltipsWindow : public Window
00775 {
00776   StringID string_id;         
00777   byte paramcount;            
00778   uint64 params[5];           
00779   bool use_left_mouse_button; 
00780 
00781   TooltipsWindow(StringID str, uint paramcount, const uint64 params[], bool use_left_mouse_button) : Window()
00782   {
00783     this->string_id = str;
00784     assert_compile(sizeof(this->params[0]) == sizeof(params[0]));
00785     assert(paramcount <= lengthof(this->params));
00786     memcpy(this->params, params, sizeof(this->params[0]) * paramcount);
00787     this->paramcount = paramcount;
00788     this->use_left_mouse_button = use_left_mouse_button;
00789 
00790     this->InitNested(&_tool_tips_desc);
00791 
00792     this->flags4 &= ~WF_WHITE_BORDER_MASK; // remove white-border from tooltip
00793   }
00794 
00795   virtual Point OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
00796   {
00797     /* Find the free screen space between the main toolbar at the top, and the statusbar at the bottom.
00798      * Add a fixed distance 2 so the tooltip floats free from both bars.
00799      */
00800     int scr_top = GetMainViewTop() + 2;
00801     int scr_bot = GetMainViewBottom() - 2;
00802 
00803     Point pt;
00804 
00805     /* Correctly position the tooltip position, watch out for window and cursor size
00806      * Clamp value to below main toolbar and above statusbar. If tooltip would
00807      * go below window, flip it so it is shown above the cursor */
00808     pt.y = Clamp(_cursor.pos.y + _cursor.size.y + _cursor.offs.y + 5, scr_top, scr_bot);
00809     if (pt.y + sm_height > scr_bot) pt.y = min(_cursor.pos.y + _cursor.offs.y - 5, scr_bot) - sm_height;
00810     pt.x = Clamp(_cursor.pos.x - (sm_width >> 1), 0, _screen.width - sm_width);
00811 
00812     return pt;
00813   }
00814 
00815   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
00816   {
00817     /* There is only one widget. */
00818     for (uint i = 0; i != this->paramcount; i++) SetDParam(i, this->params[i]);
00819 
00820     size->width  = min(GetStringBoundingBox(this->string_id).width, 194);
00821     size->height = GetStringHeight(this->string_id, size->width);
00822 
00823     /* Increase slightly to have some space around the box. */
00824     size->width  += 2 + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
00825     size->height += 2 + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
00826   }
00827 
00828   virtual void DrawWidget(const Rect &r, int widget) const
00829   {
00830     /* There is only one widget. */
00831     GfxFillRect(r.left, r.top, r.right, r.bottom, 0);
00832     GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, 0x44);
00833 
00834     for (uint arg = 0; arg < this->paramcount; arg++) {
00835       SetDParam(arg, this->params[arg]);
00836     }
00837     DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM, this->string_id, TC_FROMSTRING, SA_CENTER);
00838   }
00839 
00840 
00841   virtual void OnPaint()
00842   {
00843     this->DrawWidgets();
00844   }
00845 
00846   virtual void OnMouseLoop()
00847   {
00848     /* We can show tooltips while dragging tools. These are shown as long as
00849      * we are dragging the tool. Normal tooltips work with rmb */
00850     if (this->use_left_mouse_button ? !_left_button_down : !_right_button_down) delete this;
00851   }
00852 };
00853 
00860 void GuiShowTooltips(StringID str, uint paramcount, const uint64 params[], bool use_left_mouse_button)
00861 {
00862   DeleteWindowById(WC_TOOLTIPS, 0);
00863 
00864   if (str == STR_NULL) return;
00865 
00866   new TooltipsWindow(str, paramcount, params, use_left_mouse_button);
00867 }
00868 
00876 int DrawCargoListText(uint32 cargo_mask, const Rect &r, StringID prefix)
00877 {
00878   bool first = true;
00879   char string[512];
00880   char *b = InlineString(string, prefix);
00881 
00882   for (CargoID i = 0; i < NUM_CARGO; i++) {
00883     if (b >= lastof(string) - (1 + 2 * 4)) break; // ',' or ' ' and two calls to Utf8Encode()
00884     if (HasBit(cargo_mask, i)) {
00885       if (first) {
00886         first = false;
00887       } else {
00888         /* Add a comma if this is not the first item */
00889         *b++ = ',';
00890         *b++ = ' ';
00891       }
00892       b = InlineString(b, CargoSpec::Get(i)->name);
00893     }
00894   }
00895 
00896   /* If first is still true then no cargo is accepted */
00897   if (first) b = InlineString(b, STR_JUST_NOTHING);
00898 
00899   *b = '\0';
00900 
00901   /* Make sure we detect any buffer overflow */
00902   assert(b < endof(string));
00903 
00904   SetDParamStr(0, string);
00905   return DrawStringMultiLine(r.left, r.right, r.top, r.bottom, STR_JUST_RAW_STRING);
00906 }
00907 
00918 int DrawStationCoverageAreaText(int left, int right, int top, StationCoverageType sct, int rad, bool supplies)
00919 {
00920   TileIndex tile = TileVirtXY(_thd.pos.x, _thd.pos.y);
00921   if (tile < MapSize()) {
00922     CargoArray cargos;
00923     if (supplies) {
00924       cargos = GetProductionAroundTiles(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE, rad);
00925     } else {
00926       cargos = GetAcceptanceAroundTiles(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE, rad);
00927     }
00928 
00929     /* Convert cargo counts to a set of cargo bits, and draw the result. */
00930     uint32 cargo_mask = 0;
00931     for (CargoID i = 0; i < NUM_CARGO; i++) {
00932       switch (sct) {
00933         case SCT_PASSENGERS_ONLY: if (!IsCargoInClass(i, CC_PASSENGERS)) continue; break;
00934         case SCT_NON_PASSENGERS_ONLY: if (IsCargoInClass(i, CC_PASSENGERS)) continue; break;
00935         case SCT_ALL: break;
00936         default: NOT_REACHED();
00937       }
00938       if (cargos[i] >= (supplies ? 1U : 8U)) SetBit(cargo_mask, i);
00939     }
00940     Rect r = {left, top, right, INT32_MAX};
00941     return DrawCargoListText(cargo_mask, r, supplies ? STR_STATION_BUILD_SUPPLIES_CARGO : STR_STATION_BUILD_ACCEPTS_CARGO);
00942   }
00943 
00944   return top;
00945 }
00946 
00947 void CheckRedrawStationCoverage(const Window *w)
00948 {
00949   if (_thd.dirty & 1) {
00950     _thd.dirty &= ~1;
00951     w->SetDirty();
00952   }
00953 }
00954 
00955 /* Delete a character at the caret position in a text buf.
00956  * If backspace is set, delete the character before the caret,
00957  * else delete the character after it. */
00958 static void DelChar(Textbuf *tb, bool backspace)
00959 {
00960   WChar c;
00961   char *s = tb->buf + tb->caretpos;
00962 
00963   if (backspace) s = Utf8PrevChar(s);
00964 
00965   uint16 len = (uint16)Utf8Decode(&c, s);
00966   uint width = GetCharacterWidth(FS_NORMAL, c);
00967 
00968   tb->width  -= width;
00969   if (backspace) {
00970     tb->caretpos   -= len;
00971     tb->caretxoffs -= width;
00972   }
00973 
00974   /* Move the remaining characters over the marker */
00975   memmove(s, s + len, tb->size - (s - tb->buf) - len);
00976   tb->size -= len;
00977 }
00978 
00986 bool DeleteTextBufferChar(Textbuf *tb, int delmode)
00987 {
00988   if (delmode == WKC_BACKSPACE && tb->caretpos != 0) {
00989     DelChar(tb, true);
00990     return true;
00991   } else if (delmode == WKC_DELETE && tb->caretpos < tb->size - 1) {
00992     DelChar(tb, false);
00993     return true;
00994   }
00995 
00996   return false;
00997 }
00998 
01003 void DeleteTextBufferAll(Textbuf *tb)
01004 {
01005   memset(tb->buf, 0, tb->maxsize);
01006   tb->size = 1;
01007   tb->width = tb->caretpos = tb->caretxoffs = 0;
01008 }
01009 
01018 bool InsertTextBufferChar(Textbuf *tb, WChar key)
01019 {
01020   const byte charwidth = GetCharacterWidth(FS_NORMAL, key);
01021   uint16 len = (uint16)Utf8CharLen(key);
01022   if (tb->size + len <= tb->maxsize && (tb->maxwidth == 0 || tb->width + charwidth <= tb->maxwidth)) {
01023     memmove(tb->buf + tb->caretpos + len, tb->buf + tb->caretpos, tb->size - tb->caretpos);
01024     Utf8Encode(tb->buf + tb->caretpos, key);
01025     tb->size  += len;
01026     tb->width += charwidth;
01027 
01028     tb->caretpos   += len;
01029     tb->caretxoffs += charwidth;
01030     return true;
01031   }
01032   return false;
01033 }
01034 
01042 bool InsertTextBufferClipboard(Textbuf *tb)
01043 {
01044   char utf8_buf[512];
01045 
01046   if (!GetClipboardContents(utf8_buf, lengthof(utf8_buf))) return false;
01047 
01048   uint16 width = 0, length = 0;
01049   WChar c;
01050   for (const char *ptr = utf8_buf; (c = Utf8Consume(&ptr)) != '\0';) {
01051     if (!IsPrintable(c)) break;
01052 
01053     byte len = Utf8CharLen(c);
01054     if (tb->size + length + len > tb->maxsize) break;
01055 
01056     byte charwidth = GetCharacterWidth(FS_NORMAL, c);
01057     if (tb->maxwidth != 0 && width + tb->width + charwidth > tb->maxwidth) break;
01058 
01059     width += charwidth;
01060     length += len;
01061   }
01062 
01063   if (length == 0) return false;
01064 
01065   memmove(tb->buf + tb->caretpos + length, tb->buf + tb->caretpos, tb->size - tb->caretpos);
01066   memcpy(tb->buf + tb->caretpos, utf8_buf, length);
01067   tb->width += width;
01068   tb->caretxoffs += width;
01069 
01070   tb->size += length;
01071   tb->caretpos += length;
01072   assert(tb->size <= tb->maxsize);
01073   tb->buf[tb->size - 1] = '\0'; // terminating zero
01074 
01075   return true;
01076 }
01077 
01085 bool MoveTextBufferPos(Textbuf *tb, int navmode)
01086 {
01087   switch (navmode) {
01088     case WKC_LEFT:
01089       if (tb->caretpos != 0) {
01090         WChar c;
01091         const char *s = Utf8PrevChar(tb->buf + tb->caretpos);
01092         Utf8Decode(&c, s);
01093         tb->caretpos    = s - tb->buf; // -= (tb->buf + tb->caretpos - s)
01094         tb->caretxoffs -= GetCharacterWidth(FS_NORMAL, c);
01095 
01096         return true;
01097       }
01098       break;
01099 
01100     case WKC_RIGHT:
01101       if (tb->caretpos < tb->size - 1) {
01102         WChar c;
01103 
01104         tb->caretpos   += (uint16)Utf8Decode(&c, tb->buf + tb->caretpos);
01105         tb->caretxoffs += GetCharacterWidth(FS_NORMAL, c);
01106 
01107         return true;
01108       }
01109       break;
01110 
01111     case WKC_HOME:
01112       tb->caretpos = 0;
01113       tb->caretxoffs = 0;
01114       return true;
01115 
01116     case WKC_END:
01117       tb->caretpos = tb->size - 1;
01118       tb->caretxoffs = tb->width;
01119       return true;
01120 
01121     default:
01122       break;
01123   }
01124 
01125   return false;
01126 }
01127 
01137 void InitializeTextBuffer(Textbuf *tb, char *buf, uint16 maxsize, uint16 maxwidth)
01138 {
01139   assert(maxsize != 0);
01140 
01141   tb->buf      = buf;
01142   tb->maxsize  = maxsize;
01143   tb->maxwidth = maxwidth;
01144   tb->caret    = true;
01145   UpdateTextBufferSize(tb);
01146 }
01147 
01154 void UpdateTextBufferSize(Textbuf *tb)
01155 {
01156   const char *buf = tb->buf;
01157 
01158   tb->width = 0;
01159   tb->size = 1; // terminating zero
01160 
01161   WChar c;
01162   while ((c = Utf8Consume(&buf)) != '\0') {
01163     tb->width += GetCharacterWidth(FS_NORMAL, c);
01164     tb->size += Utf8CharLen(c);
01165   }
01166 
01167   assert(tb->size <= tb->maxsize);
01168 
01169   tb->caretpos = tb->size - 1;
01170   tb->caretxoffs = tb->width;
01171 }
01172 
01173 bool HandleCaret(Textbuf *tb)
01174 {
01175   /* caret changed? */
01176   bool b = !!(_caret_timer & 0x20);
01177 
01178   if (b != tb->caret) {
01179     tb->caret = b;
01180     return true;
01181   }
01182   return false;
01183 }
01184 
01185 bool QueryString::HasEditBoxFocus(const Window *w, int wid) const
01186 {
01187   if (w->IsWidgetGloballyFocused(wid)) return true;
01188   if (w->window_class != WC_OSK || _focused_window != w->parent) return false;
01189   return w->parent->nested_focus != NULL && w->parent->nested_focus->type == WWT_EDITBOX;
01190 }
01191 
01192 HandleEditBoxResult QueryString::HandleEditBoxKey(Window *w, int wid, uint16 key, uint16 keycode, Window::EventState &state)
01193 {
01194   if (!QueryString::HasEditBoxFocus(w, wid)) return HEBR_NOT_FOCUSED;
01195 
01196   state = Window::ES_HANDLED;
01197 
01198   switch (keycode) {
01199     case WKC_ESC: return HEBR_CANCEL;
01200 
01201     case WKC_RETURN: case WKC_NUM_ENTER: return HEBR_CONFIRM;
01202 
01203 #ifdef WITH_COCOA
01204     case (WKC_META | 'V'):
01205 #endif
01206     case (WKC_CTRL | 'V'):
01207       if (InsertTextBufferClipboard(&this->text)) w->SetWidgetDirty(wid);
01208       break;
01209 
01210 #ifdef WITH_COCOA
01211     case (WKC_META | 'U'):
01212 #endif
01213     case (WKC_CTRL | 'U'):
01214       DeleteTextBufferAll(&this->text);
01215       w->SetWidgetDirty(wid);
01216       break;
01217 
01218     case WKC_BACKSPACE: case WKC_DELETE:
01219       if (DeleteTextBufferChar(&this->text, keycode)) w->SetWidgetDirty(wid);
01220       break;
01221 
01222     case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
01223       if (MoveTextBufferPos(&this->text, keycode)) w->SetWidgetDirty(wid);
01224       break;
01225 
01226     default:
01227       if (IsValidChar(key, this->afilter)) {
01228         if (InsertTextBufferChar(&this->text, key)) w->SetWidgetDirty(wid);
01229       } else {
01230         state = Window::ES_NOT_HANDLED;
01231       }
01232   }
01233 
01234   return HEBR_EDITING;
01235 }
01236 
01237 void QueryString::HandleEditBox(Window *w, int wid)
01238 {
01239   if (HasEditBoxFocus(w, wid) && HandleCaret(&this->text)) {
01240     w->SetWidgetDirty(wid);
01241     /* When we're not the OSK, notify 'our' OSK to redraw the widget,
01242      * so the caret changes appropriately. */
01243     if (w->window_class != WC_OSK) {
01244       Window *w_osk = FindWindowById(WC_OSK, 0);
01245       if (w_osk != NULL && w_osk->parent == w) w_osk->InvalidateData();
01246     }
01247   }
01248 }
01249 
01250 void QueryString::DrawEditBox(Window *w, int wid)
01251 {
01252   const NWidgetBase *wi = w->GetWidget<NWidgetBase>(wid);
01253 
01254   assert((wi->type & WWT_MASK) == WWT_EDITBOX);
01255   int left   = wi->pos_x;
01256   int right  = wi->pos_x + wi->current_x - 1;
01257   int top    = wi->pos_y;
01258   int bottom = wi->pos_y + wi->current_y - 1;
01259 
01260   GfxFillRect(left + 1, top + 1, right - 1, bottom - 1, 215);
01261 
01262   /* Limit the drawing of the string inside the widget boundaries */
01263   DrawPixelInfo dpi;
01264   if (!FillDrawPixelInfo(&dpi, left + WD_FRAMERECT_LEFT, top + WD_FRAMERECT_TOP, right - left - WD_FRAMERECT_RIGHT, bottom - top - WD_FRAMERECT_BOTTOM)) return;
01265 
01266   DrawPixelInfo *old_dpi = _cur_dpi;
01267   _cur_dpi = &dpi;
01268 
01269   /* We will take the current widget length as maximum width, with a small
01270    * space reserved at the end for the caret to show */
01271   const Textbuf *tb = &this->text;
01272   int delta = min(0, (right - left) - tb->width - 10);
01273 
01274   if (tb->caretxoffs + delta < 0) delta = -tb->caretxoffs;
01275 
01276   DrawString(delta, tb->width, 0, tb->buf, TC_YELLOW);
01277   if (HasEditBoxFocus(w, wid) && tb->caret) {
01278     int caret_width = GetStringBoundingBox("_").width;
01279     DrawString(tb->caretxoffs + delta, tb->caretxoffs + delta + caret_width, 0, "_", TC_WHITE);
01280   }
01281 
01282   _cur_dpi = old_dpi;
01283 }
01284 
01285 HandleEditBoxResult QueryStringBaseWindow::HandleEditBoxKey(int wid, uint16 key, uint16 keycode, EventState &state)
01286 {
01287   return this->QueryString::HandleEditBoxKey(this, wid, key, keycode, state);
01288 }
01289 
01290 void QueryStringBaseWindow::HandleEditBox(int wid)
01291 {
01292   this->QueryString::HandleEditBox(this, wid);
01293 }
01294 
01295 void QueryStringBaseWindow::DrawEditBox(int wid)
01296 {
01297   this->QueryString::DrawEditBox(this, wid);
01298 }
01299 
01300 void QueryStringBaseWindow::OnOpenOSKWindow(int wid)
01301 {
01302   ShowOnScreenKeyboard(this, wid, 0, 0);
01303 }
01304 
01306 enum QueryStringWidgets {
01307   QUERY_STR_WIDGET_CAPTION,
01308   QUERY_STR_WIDGET_TEXT,
01309   QUERY_STR_WIDGET_DEFAULT,
01310   QUERY_STR_WIDGET_CANCEL,
01311   QUERY_STR_WIDGET_OK
01312 };
01313 
01315 struct QueryStringWindow : public QueryStringBaseWindow
01316 {
01317   QueryStringFlags flags; 
01318 
01319   QueryStringWindow(StringID str, StringID caption, uint maxsize, uint maxwidth, const WindowDesc *desc, Window *parent, CharSetFilter afilter, QueryStringFlags flags) :
01320       QueryStringBaseWindow(maxsize)
01321   {
01322     GetString(this->edit_str_buf, str, &this->edit_str_buf[maxsize - 1]);
01323     this->edit_str_buf[maxsize - 1] = '\0';
01324 
01325     if ((flags & QSF_ACCEPT_UNCHANGED) == 0) this->orig = strdup(this->edit_str_buf);
01326 
01327     this->caption = caption;
01328     this->afilter = afilter;
01329     this->flags = flags;
01330     InitializeTextBuffer(&this->text, this->edit_str_buf, maxsize, maxwidth);
01331 
01332     this->InitNested(desc);
01333 
01334     this->parent = parent;
01335 
01336     this->SetFocusedWidget(QUERY_STR_WIDGET_TEXT);
01337     this->LowerWidget(QUERY_STR_WIDGET_TEXT);
01338   }
01339 
01340   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
01341   {
01342     if (widget == QUERY_STR_WIDGET_DEFAULT && (this->flags & QSF_ENABLE_DEFAULT) == 0) {
01343       this->GetWidget<NWidgetCore>(widget)->SetFill(0, 1);
01344       size->width = 0;
01345     }
01346   }
01347 
01348   virtual void OnPaint()
01349   {
01350     this->DrawWidgets();
01351 
01352     this->DrawEditBox(QUERY_STR_WIDGET_TEXT);
01353   }
01354 
01355   virtual void SetStringParameters(int widget) const
01356   {
01357     if (widget == QUERY_STR_WIDGET_CAPTION) SetDParam(0, this->caption);
01358   }
01359 
01360   void OnOk()
01361   {
01362     if (this->orig == NULL || strcmp(this->text.buf, this->orig) != 0) {
01363       /* If the parent is NULL, the editbox is handled by general function
01364        * HandleOnEditText */
01365       if (this->parent != NULL) {
01366         this->parent->OnQueryTextFinished(this->text.buf);
01367       } else {
01368         HandleOnEditText(this->text.buf);
01369       }
01370       this->handled = true;
01371     }
01372   }
01373 
01374   virtual void OnClick(Point pt, int widget)
01375   {
01376     switch (widget) {
01377       case QUERY_STR_WIDGET_DEFAULT:
01378         this->text.buf[0] = '\0';
01379         /* Fallthrough */
01380       case QUERY_STR_WIDGET_OK:
01381         this->OnOk();
01382         /* Fallthrough */
01383       case QUERY_STR_WIDGET_CANCEL:
01384         delete this;
01385         break;
01386     }
01387   }
01388 
01389   virtual void OnMouseLoop()
01390   {
01391     this->HandleEditBox(QUERY_STR_WIDGET_TEXT);
01392   }
01393 
01394   virtual EventState OnKeyPress(uint16 key, uint16 keycode)
01395   {
01396     EventState state = ES_NOT_HANDLED;
01397     switch (this->HandleEditBoxKey(QUERY_STR_WIDGET_TEXT, key, keycode, state)) {
01398       default: NOT_REACHED();
01399       case HEBR_EDITING: {
01400         Window *osk = FindWindowById(WC_OSK, 0);
01401         if (osk != NULL && osk->parent == this) osk->InvalidateData();
01402       } break;
01403       case HEBR_CONFIRM: this->OnOk();
01404       /* FALL THROUGH */
01405       case HEBR_CANCEL: delete this; break; // close window, abandon changes
01406       case HEBR_NOT_FOCUSED: break;
01407     }
01408     return state;
01409   }
01410 
01411   virtual void OnOpenOSKWindow(int wid)
01412   {
01413     ShowOnScreenKeyboard(this, wid, QUERY_STR_WIDGET_CANCEL, QUERY_STR_WIDGET_OK);
01414   }
01415 
01416   ~QueryStringWindow()
01417   {
01418     if (!this->handled && this->parent != NULL) {
01419       Window *parent = this->parent;
01420       this->parent = NULL; // so parent doesn't try to delete us again
01421       parent->OnQueryTextFinished(NULL);
01422     }
01423   }
01424 };
01425 
01426 static const NWidgetPart _nested_query_string_widgets[] = {
01427   NWidget(NWID_HORIZONTAL),
01428     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
01429     NWidget(WWT_CAPTION, COLOUR_GREY, QUERY_STR_WIDGET_CAPTION), SetDataTip(STR_WHITE_STRING, STR_NULL),
01430   EndContainer(),
01431   NWidget(WWT_PANEL, COLOUR_GREY),
01432     NWidget(WWT_EDITBOX, COLOUR_GREY, QUERY_STR_WIDGET_TEXT), SetMinimalSize(256, 12), SetFill(1, 1), SetPadding(2, 2, 2, 2),
01433   EndContainer(),
01434   NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
01435     NWidget(WWT_TEXTBTN, COLOUR_GREY, QUERY_STR_WIDGET_DEFAULT), SetMinimalSize(87, 12), SetFill(1, 1), SetDataTip(STR_BUTTON_DEFAULT, STR_NULL),
01436     NWidget(WWT_TEXTBTN, COLOUR_GREY, QUERY_STR_WIDGET_CANCEL), SetMinimalSize(86, 12), SetFill(1, 1), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
01437     NWidget(WWT_TEXTBTN, COLOUR_GREY, QUERY_STR_WIDGET_OK), SetMinimalSize(87, 12), SetFill(1, 1), SetDataTip(STR_BUTTON_OK, STR_NULL),
01438   EndContainer(),
01439 };
01440 
01441 static const WindowDesc _query_string_desc(
01442   WDP_AUTO, 0, 0,
01443   WC_QUERY_STRING, WC_NONE,
01444   0,
01445   _nested_query_string_widgets, lengthof(_nested_query_string_widgets)
01446 );
01447 
01458 void ShowQueryString(StringID str, StringID caption, uint maxsize, uint maxwidth, Window *parent, CharSetFilter afilter, QueryStringFlags flags)
01459 {
01460   DeleteWindowById(WC_QUERY_STRING, 0);
01461   new QueryStringWindow(str, caption, maxsize, maxwidth, &_query_string_desc, parent, afilter, flags);
01462 }
01463 
01464 
01465 enum QueryWidgets {
01466   QUERY_WIDGET_CAPTION,
01467   QUERY_WIDGET_TEXT,
01468   QUERY_WIDGET_NO,
01469   QUERY_WIDGET_YES
01470 };
01471 
01475 struct QueryWindow : public Window {
01476   QueryCallbackProc *proc; 
01477   uint64 params[10];       
01478   StringID message;        
01479   StringID caption;        
01480 
01481   QueryWindow(const WindowDesc *desc, StringID caption, StringID message, Window *parent, QueryCallbackProc *callback) : Window()
01482   {
01483     /* Create a backup of the variadic arguments to strings because it will be
01484      * overridden pretty often. We will copy these back for drawing */
01485     CopyOutDParam(this->params, 0, lengthof(this->params));
01486     this->caption = caption;
01487     this->message = message;
01488     this->proc    = callback;
01489 
01490     this->InitNested(desc);
01491 
01492     if (parent == NULL) parent = FindWindowById(WC_MAIN_WINDOW, 0);
01493     this->parent = parent;
01494     this->left = parent->left + (parent->width / 2) - (this->width / 2);
01495     this->top = parent->top + (parent->height / 2) - (this->height / 2);
01496   }
01497 
01498   ~QueryWindow()
01499   {
01500     if (this->proc != NULL) this->proc(this->parent, false);
01501   }
01502 
01503   virtual void SetStringParameters(int widget) const
01504   {
01505     switch (widget) {
01506       case QUERY_WIDGET_CAPTION:
01507         CopyInDParam(1, this->params, lengthof(this->params));
01508         SetDParam(0, this->caption);
01509         break;
01510 
01511       case QUERY_WIDGET_TEXT:
01512         CopyInDParam(0, this->params, lengthof(this->params));
01513         break;
01514     }
01515   }
01516 
01517   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
01518   {
01519     if (widget != QUERY_WIDGET_TEXT) return;
01520 
01521     Dimension d = GetStringMultiLineBoundingBox(this->message, *size);
01522     d.width += padding.width;
01523     d.height += padding.height;
01524     *size = d;
01525   }
01526 
01527   virtual void DrawWidget(const Rect &r, int widget) const
01528   {
01529     if (widget != QUERY_WIDGET_TEXT) return;
01530 
01531     DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->message, TC_FROMSTRING, SA_CENTER);
01532   }
01533 
01534   virtual void OnPaint()
01535   {
01536     this->DrawWidgets();
01537   }
01538 
01539   virtual void OnClick(Point pt, int widget)
01540   {
01541     switch (widget) {
01542       case QUERY_WIDGET_YES: {
01543         /* in the Generate New World window, clicking 'Yes' causes
01544          * DeleteNonVitalWindows() to be called - we shouldn't be in a window then */
01545         QueryCallbackProc *proc = this->proc;
01546         Window *parent = this->parent;
01547         /* Prevent the destructor calling the callback function */
01548         this->proc = NULL;
01549         delete this;
01550         if (proc != NULL) {
01551           proc(parent, true);
01552           proc = NULL;
01553         }
01554       } break;
01555       case QUERY_WIDGET_NO:
01556         delete this;
01557         break;
01558     }
01559   }
01560 
01561   virtual EventState OnKeyPress(uint16 key, uint16 keycode)
01562   {
01563     /* ESC closes the window, Enter confirms the action */
01564     switch (keycode) {
01565       case WKC_RETURN:
01566       case WKC_NUM_ENTER:
01567         if (this->proc != NULL) {
01568           this->proc(this->parent, true);
01569           this->proc = NULL;
01570         }
01571         /* Fallthrough */
01572       case WKC_ESC:
01573         delete this;
01574         return ES_HANDLED;
01575     }
01576     return ES_NOT_HANDLED;
01577   }
01578 };
01579 
01580 static const NWidgetPart _nested_query_widgets[] = {
01581   NWidget(NWID_HORIZONTAL),
01582     NWidget(WWT_CLOSEBOX, COLOUR_RED),
01583     NWidget(WWT_CAPTION, COLOUR_RED, QUERY_WIDGET_CAPTION), SetDataTip(STR_JUST_STRING, STR_NULL),
01584   EndContainer(),
01585   NWidget(WWT_PANEL, COLOUR_RED), SetPIP(8, 15, 8),
01586     NWidget(WWT_TEXT, COLOUR_RED, QUERY_WIDGET_TEXT), SetMinimalSize(200, 12),
01587     NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(20, 29, 20),
01588       NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, QUERY_WIDGET_NO), SetMinimalSize(71, 12), SetDataTip(STR_QUIT_NO, STR_NULL),
01589       NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, QUERY_WIDGET_YES), SetMinimalSize(71, 12), SetDataTip(STR_QUIT_YES, STR_NULL),
01590     EndContainer(),
01591   EndContainer(),
01592 };
01593 
01594 static const WindowDesc _query_desc(
01595   WDP_CENTER, 0, 0,
01596   WC_CONFIRM_POPUP_QUERY, WC_NONE,
01597   WDF_UNCLICK_BUTTONS | WDF_MODAL,
01598   _nested_query_widgets, lengthof(_nested_query_widgets)
01599 );
01600 
01609 void ShowQuery(StringID caption, StringID message, Window *parent, QueryCallbackProc *callback)
01610 {
01611   new QueryWindow(&_query_desc, caption, message, parent, callback);
01612 }
01613 
01614 
01615 enum SaveLoadWindowWidgets {
01616   SLWW_WINDOWTITLE,
01617   SLWW_SORT_BYNAME,
01618   SLWW_SORT_BYDATE,
01619   SLWW_BACKGROUND,
01620   SLWW_FILE_BACKGROUND,
01621   SLWW_HOME_BUTTON,
01622   SLWW_DRIVES_DIRECTORIES_LIST,
01623   SLWW_SCROLLBAR,
01624   SLWW_CONTENT_DOWNLOAD,     
01625   SLWW_SAVE_OSK_TITLE,       
01626   SLWW_DELETE_SELECTION,     
01627   SLWW_SAVE_GAME,            
01628   SLWW_CONTENT_DOWNLOAD_SEL, 
01629 };
01630 
01631 static const NWidgetPart _nested_load_dialog_widgets[] = {
01632   NWidget(NWID_HORIZONTAL),
01633     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
01634     NWidget(WWT_CAPTION, COLOUR_GREY, SLWW_WINDOWTITLE),
01635   EndContainer(),
01636   NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
01637     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_SORT_BYNAME), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
01638     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_SORT_BYDATE), SetDataTip(STR_SORT_BY_CAPTION_DATE, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
01639   EndContainer(),
01640   NWidget(WWT_PANEL, COLOUR_GREY, SLWW_BACKGROUND), SetFill(1, 0), SetResize(1, 0), EndContainer(),
01641   NWidget(WWT_PANEL, COLOUR_GREY, SLWW_FILE_BACKGROUND),
01642     NWidget(NWID_HORIZONTAL),
01643       NWidget(NWID_VERTICAL),
01644         NWidget(WWT_INSET, COLOUR_GREY, SLWW_DRIVES_DIRECTORIES_LIST), SetFill(1, 1), SetPadding(2, 1, 2, 2),
01645                     SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), EndContainer(),
01646         NWidget(NWID_SELECTION, INVALID_COLOUR, SLWW_CONTENT_DOWNLOAD_SEL),
01647           NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_CONTENT_DOWNLOAD), SetResize(1, 0),
01648                     SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
01649         EndContainer(),
01650       EndContainer(),
01651       NWidget(NWID_VERTICAL),
01652         NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, SLWW_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON),
01653         NWidget(WWT_SCROLLBAR, COLOUR_GREY, SLWW_SCROLLBAR),
01654         NWidget(WWT_RESIZEBOX, COLOUR_GREY),
01655       EndContainer(),
01656     EndContainer(),
01657   EndContainer(),
01658 };
01659 
01660 static const NWidgetPart _nested_save_dialog_widgets[] = {
01661   NWidget(NWID_HORIZONTAL),
01662     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
01663     NWidget(WWT_CAPTION, COLOUR_GREY, SLWW_WINDOWTITLE),
01664   EndContainer(),
01665   NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
01666     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_SORT_BYNAME), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
01667     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_SORT_BYDATE), SetDataTip(STR_SORT_BY_CAPTION_DATE, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
01668   EndContainer(),
01669   NWidget(WWT_PANEL, COLOUR_GREY, SLWW_BACKGROUND), SetFill(1, 0), SetResize(1, 0), EndContainer(),
01670   NWidget(WWT_PANEL, COLOUR_GREY, SLWW_FILE_BACKGROUND),
01671     NWidget(NWID_HORIZONTAL),
01672       NWidget(WWT_INSET, COLOUR_GREY, SLWW_DRIVES_DIRECTORIES_LIST), SetPadding(2, 1, 0, 2),
01673                     SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), EndContainer(),
01674       NWidget(NWID_VERTICAL),
01675         NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, SLWW_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON),
01676         NWidget(WWT_SCROLLBAR, COLOUR_GREY, SLWW_SCROLLBAR),
01677       EndContainer(),
01678     EndContainer(),
01679     NWidget(WWT_EDITBOX, COLOUR_GREY, SLWW_SAVE_OSK_TITLE), SetPadding(3, 2, 2, 2), SetFill(1, 0), SetResize(1, 0),
01680                     SetDataTip(STR_SAVELOAD_OSKTITLE, STR_SAVELOAD_EDITBOX_TOOLTIP),
01681   EndContainer(),
01682   NWidget(NWID_HORIZONTAL),
01683     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_DELETE_SELECTION), SetDataTip(STR_SAVELOAD_DELETE_BUTTON, STR_SAVELOAD_DELETE_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
01684     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SLWW_SAVE_GAME),        SetDataTip(STR_SAVELOAD_SAVE_BUTTON, STR_SAVELOAD_SAVE_TOOLTIP),     SetFill(1, 0), SetResize(1, 0),
01685     NWidget(WWT_RESIZEBOX, COLOUR_GREY),
01686   EndContainer(),
01687 };
01688 
01689 /* Colours for fios types */
01690 const TextColour _fios_colours[] = {
01691   TC_LIGHT_BLUE, TC_DARK_GREEN,  TC_DARK_GREEN, TC_ORANGE, TC_LIGHT_BROWN,
01692   TC_ORANGE,     TC_LIGHT_BROWN, TC_ORANGE,     TC_ORANGE, TC_YELLOW
01693 };
01694 
01695 void BuildFileList()
01696 {
01697   _fios_path_changed = true;
01698   FiosFreeSavegameList();
01699 
01700   switch (_saveload_mode) {
01701     case SLD_NEW_GAME:
01702     case SLD_LOAD_SCENARIO:
01703     case SLD_SAVE_SCENARIO:
01704       FiosGetScenarioList(_saveload_mode); break;
01705     case SLD_LOAD_HEIGHTMAP:
01706       FiosGetHeightmapList(_saveload_mode); break;
01707 
01708     default: FiosGetSavegameList(_saveload_mode); break;
01709   }
01710 }
01711 
01712 static void MakeSortedSaveGameList()
01713 {
01714   uint sort_start = 0;
01715   uint sort_end = 0;
01716 
01717   /* Directories are always above the files (FIOS_TYPE_DIR)
01718    * Drives (A:\ (windows only) are always under the files (FIOS_TYPE_DRIVE)
01719    * Only sort savegames/scenarios, not directories
01720    */
01721   for (const FiosItem *item = _fios_items.Begin(); item != _fios_items.End(); item++) {
01722     switch (item->type) {
01723       case FIOS_TYPE_DIR:    sort_start++; break;
01724       case FIOS_TYPE_PARENT: sort_start++; break;
01725       case FIOS_TYPE_DRIVE:  sort_end++;   break;
01726       default: break;
01727     }
01728   }
01729 
01730   uint s_amount = _fios_items.Length() - sort_start - sort_end;
01731   QSortT(_fios_items.Get(sort_start), s_amount, CompareFiosItems);
01732 }
01733 
01734 extern void StartupEngines();
01735 
01736 struct SaveLoadWindow : public QueryStringBaseWindow {
01737 private:
01738   FiosItem o_dir;
01739 public:
01740 
01741   void GenerateFileName()
01742   {
01743     GenerateDefaultSaveName(this->edit_str_buf, &this->edit_str_buf[this->edit_str_size - 1]);
01744   }
01745 
01746   SaveLoadWindow(const WindowDesc *desc, SaveLoadDialogMode mode) : QueryStringBaseWindow(64)
01747   {
01748     static const StringID saveload_captions[] = {
01749       STR_SAVELOAD_LOAD_CAPTION,
01750       STR_SAVELOAD_LOAD_SCENARIO,
01751       STR_SAVELOAD_SAVE_CAPTION,
01752       STR_SAVELOAD_SAVE_SCENARIO,
01753       STR_SAVELOAD_LOAD_HEIGHTMAP,
01754     };
01755     assert((uint)mode < lengthof(saveload_captions));
01756 
01757     /* Use an array to define what will be the current file type being handled
01758      * by current file mode */
01759     switch (mode) {
01760       case SLD_SAVE_GAME:     this->GenerateFileName(); break;
01761       case SLD_SAVE_SCENARIO: strecpy(this->edit_str_buf, "UNNAMED", &this->edit_str_buf[edit_str_size - 1]); break;
01762       default:                break;
01763     }
01764 
01765     this->afilter = CS_ALPHANUMERAL;
01766     InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, 240);
01767 
01768     this->CreateNestedTree(desc);
01769     if (mode == SLD_LOAD_GAME) this->GetWidget<NWidgetStacked>(SLWW_CONTENT_DOWNLOAD_SEL)->SetDisplayedPlane(SZSP_HORIZONTAL);
01770     this->GetWidget<NWidgetCore>(SLWW_WINDOWTITLE)->widget_data = saveload_captions[mode];
01771 
01772     this->FinishInitNested(desc, 0);
01773 
01774     this->LowerWidget(SLWW_DRIVES_DIRECTORIES_LIST);
01775 
01776     /* pause is only used in single-player, non-editor mode, non-menu mode. It
01777      * will be unpaused in the WE_DESTROY event handler. */
01778     if (_game_mode != GM_MENU && !_networking && _game_mode != GM_EDITOR) {
01779       DoCommandP(0, PM_PAUSED_SAVELOAD, 1, CMD_PAUSE);
01780     }
01781     SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0);
01782 
01783     BuildFileList();
01784 
01785     ResetObjectToPlace();
01786 
01787     o_dir.type = FIOS_TYPE_DIRECT;
01788     switch (_saveload_mode) {
01789       case SLD_SAVE_GAME:
01790       case SLD_LOAD_GAME:
01791         FioGetDirectory(o_dir.name, lengthof(o_dir.name), SAVE_DIR);
01792         break;
01793 
01794       case SLD_SAVE_SCENARIO:
01795       case SLD_LOAD_SCENARIO:
01796         FioGetDirectory(o_dir.name, lengthof(o_dir.name), SCENARIO_DIR);
01797         break;
01798 
01799       case SLD_LOAD_HEIGHTMAP:
01800         FioGetDirectory(o_dir.name, lengthof(o_dir.name), HEIGHTMAP_DIR);
01801         break;
01802 
01803       default:
01804         strecpy(o_dir.name, _personal_dir, lastof(o_dir.name));
01805     }
01806 
01807     /* Focus the edit box by default in the save windows */
01808     if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
01809       this->SetFocusedWidget(SLWW_SAVE_OSK_TITLE);
01810     }
01811   }
01812 
01813   virtual ~SaveLoadWindow()
01814   {
01815     /* pause is only used in single-player, non-editor mode, non menu mode */
01816     if (!_networking && _game_mode != GM_EDITOR && _game_mode != GM_MENU) {
01817       DoCommandP(0, PM_PAUSED_SAVELOAD, 0, CMD_PAUSE);
01818     }
01819     FiosFreeSavegameList();
01820   }
01821 
01822   virtual void DrawWidget(const Rect &r, int widget) const
01823   {
01824     switch (widget) {
01825       case SLWW_SORT_BYNAME:
01826       case SLWW_SORT_BYDATE:
01827         if (((_savegame_sort_order & SORT_BY_NAME) != 0) == (widget == SLWW_SORT_BYNAME)) {
01828           this->DrawSortButtonState(widget, _savegame_sort_order & SORT_DESCENDING ? SBS_DOWN : SBS_UP);
01829         }
01830         break;
01831 
01832       case SLWW_BACKGROUND: {
01833         static const char *path = NULL;
01834         static StringID str = STR_ERROR_UNABLE_TO_READ_DRIVE;
01835         static uint64 tot = 0;
01836 
01837         if (_fios_path_changed) {
01838           str = FiosGetDescText(&path, &tot);
01839           _fios_path_changed = false;
01840         }
01841 
01842         if (str != STR_ERROR_UNABLE_TO_READ_DRIVE) SetDParam(0, tot);
01843         DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP, str);
01844         DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, path, TC_BLACK);
01845       } break;
01846 
01847       case SLWW_DRIVES_DIRECTORIES_LIST: {
01848         GfxFillRect(r.left + 1, r.top + 1, r.right, r.bottom, 0xD7);
01849 
01850         uint y = r.top + WD_FRAMERECT_TOP;
01851         for (uint pos = this->vscroll.GetPosition(); pos < _fios_items.Length(); pos++) {
01852           const FiosItem *item = _fios_items.Get(pos);
01853 
01854           DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, item->title, _fios_colours[item->type]);
01855           y += this->resize.step_height;
01856           if (y >= this->vscroll.GetCapacity() * this->resize.step_height + r.top + WD_FRAMERECT_TOP) break;
01857         }
01858       } break;
01859     }
01860   }
01861 
01862   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
01863   {
01864     switch (widget) {
01865       case SLWW_BACKGROUND:
01866         size->height = 2 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
01867         break;
01868 
01869       case SLWW_DRIVES_DIRECTORIES_LIST:
01870         resize->height = FONT_HEIGHT_NORMAL;
01871         size->height = resize->height * 10 + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
01872         break;
01873     }
01874   }
01875 
01876   virtual void OnPaint()
01877   {
01878     if (_savegame_sort_dirty) {
01879       _savegame_sort_dirty = false;
01880       MakeSortedSaveGameList();
01881     }
01882 
01883     this->vscroll.SetCount(_fios_items.Length());
01884     this->DrawWidgets();
01885 
01886     if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
01887       this->DrawEditBox(SLWW_SAVE_OSK_TITLE);
01888     }
01889   }
01890 
01891   virtual void OnClick(Point pt, int widget)
01892   {
01893     switch (widget) {
01894       case SLWW_SORT_BYNAME: // Sort save names by name
01895         _savegame_sort_order = (_savegame_sort_order == SORT_BY_NAME) ?
01896           SORT_BY_NAME | SORT_DESCENDING : SORT_BY_NAME;
01897         _savegame_sort_dirty = true;
01898         this->SetDirty();
01899         break;
01900 
01901       case SLWW_SORT_BYDATE: // Sort save names by date
01902         _savegame_sort_order = (_savegame_sort_order == SORT_BY_DATE) ?
01903           SORT_BY_DATE | SORT_DESCENDING : SORT_BY_DATE;
01904         _savegame_sort_dirty = true;
01905         this->SetDirty();
01906         break;
01907 
01908       case SLWW_HOME_BUTTON: // OpenTTD 'button', jumps to OpenTTD directory
01909         FiosBrowseTo(&o_dir);
01910         this->SetDirty();
01911         BuildFileList();
01912         break;
01913 
01914       case SLWW_DRIVES_DIRECTORIES_LIST: { // Click the listbox
01915         int y = (pt.y - this->GetWidget<NWidgetBase>(SLWW_DRIVES_DIRECTORIES_LIST)->pos_y - WD_FRAMERECT_TOP) / this->resize.step_height;
01916 
01917         if (y < 0 || (y += this->vscroll.GetPosition()) >= this->vscroll.GetCount()) return;
01918 
01919         const FiosItem *file = _fios_items.Get(y);
01920 
01921         const char *name = FiosBrowseTo(file);
01922         if (name != NULL) {
01923           if (_saveload_mode == SLD_LOAD_GAME || _saveload_mode == SLD_LOAD_SCENARIO) {
01924             _switch_mode = (_game_mode == GM_EDITOR) ? SM_LOAD_SCENARIO : SM_LOAD;
01925 
01926             SetFiosType(file->type);
01927             strecpy(_file_to_saveload.name, name, lastof(_file_to_saveload.name));
01928             strecpy(_file_to_saveload.title, file->title, lastof(_file_to_saveload.title));
01929 
01930             delete this;
01931           } else if (_saveload_mode == SLD_LOAD_HEIGHTMAP) {
01932             SetFiosType(file->type);
01933             strecpy(_file_to_saveload.name, name, lastof(_file_to_saveload.name));
01934             strecpy(_file_to_saveload.title, file->title, lastof(_file_to_saveload.title));
01935 
01936             delete this;
01937             ShowHeightmapLoad();
01938           } else {
01939             /* SLD_SAVE_GAME, SLD_SAVE_SCENARIO copy clicked name to editbox */
01940             ttd_strlcpy(this->text.buf, file->title, this->text.maxsize);
01941             UpdateTextBufferSize(&this->text);
01942             this->SetWidgetDirty(SLWW_SAVE_OSK_TITLE);
01943           }
01944         } else {
01945           /* Changed directory, need repaint. */
01946           this->SetDirty();
01947           BuildFileList();
01948         }
01949         break;
01950       }
01951 
01952       case SLWW_CONTENT_DOWNLOAD:
01953         if (!_network_available) {
01954           ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE, INVALID_STRING_ID, 0, 0);
01955         } else {
01956 #if defined(ENABLE_NETWORK)
01957           switch (_saveload_mode) {
01958             default: NOT_REACHED();
01959             case SLD_LOAD_SCENARIO:  ShowNetworkContentListWindow(NULL, CONTENT_TYPE_SCENARIO);  break;
01960             case SLD_LOAD_HEIGHTMAP: ShowNetworkContentListWindow(NULL, CONTENT_TYPE_HEIGHTMAP); break;
01961           }
01962 #endif
01963         }
01964         break;
01965 
01966       case SLWW_DELETE_SELECTION: case SLWW_SAVE_GAME: // Delete, Save game
01967         break;
01968     }
01969   }
01970 
01971   virtual void OnMouseLoop()
01972   {
01973     if (_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) {
01974       this->HandleEditBox(SLWW_SAVE_OSK_TITLE);
01975     }
01976   }
01977 
01978   virtual EventState OnKeyPress(uint16 key, uint16 keycode)
01979   {
01980     if (keycode == WKC_ESC) {
01981       delete this;
01982       return ES_HANDLED;
01983     }
01984 
01985     EventState state = ES_NOT_HANDLED;
01986     if ((_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO) &&
01987         this->HandleEditBoxKey(SLWW_SAVE_OSK_TITLE, key, keycode, state) == HEBR_CONFIRM) {
01988       this->HandleButtonClick(SLWW_SAVE_GAME);
01989     }
01990 
01991     return state;
01992   }
01993 
01994   virtual void OnTimeout()
01995   {
01996     /* This test protects against using widgets 11 and 12 which are only available
01997      * in those two saveload mode */
01998     if (!(_saveload_mode == SLD_SAVE_GAME || _saveload_mode == SLD_SAVE_SCENARIO)) return;
01999 
02000     if (this->IsWidgetLowered(SLWW_DELETE_SELECTION)) { // Delete button clicked
02001       if (!FiosDelete(this->text.buf)) {
02002         ShowErrorMessage(STR_ERROR_UNABLE_TO_DELETE_FILE, INVALID_STRING_ID, 0, 0);
02003       } else {
02004         BuildFileList();
02005         /* Reset file name to current date on successful delete */
02006         if (_saveload_mode == SLD_SAVE_GAME) GenerateFileName();
02007       }
02008 
02009       UpdateTextBufferSize(&this->text);
02010       this->SetDirty();
02011     } else if (this->IsWidgetLowered(SLWW_SAVE_GAME)) { // Save button clicked
02012       _switch_mode = SM_SAVE;
02013       FiosMakeSavegameName(_file_to_saveload.name, this->text.buf, sizeof(_file_to_saveload.name));
02014 
02015       /* In the editor set up the vehicle engines correctly (date might have changed) */
02016       if (_game_mode == GM_EDITOR) StartupEngines();
02017     }
02018   }
02019 
02020   virtual void OnResize()
02021   {
02022     this->vscroll.SetCapacityFromWidget(this, SLWW_DRIVES_DIRECTORIES_LIST);
02023   }
02024 
02025   virtual void OnInvalidateData(int data)
02026   {
02027     BuildFileList();
02028   }
02029 };
02030 
02031 static const WindowDesc _load_dialog_desc(
02032   WDP_CENTER, 257, 294,
02033   WC_SAVELOAD, WC_NONE,
02034   WDF_UNCLICK_BUTTONS,
02035   _nested_load_dialog_widgets, lengthof(_nested_load_dialog_widgets)
02036 );
02037 
02038 static const WindowDesc _save_dialog_desc(
02039   WDP_CENTER, 257, 320,
02040   WC_SAVELOAD, WC_NONE,
02041   WDF_UNCLICK_BUTTONS,
02042   _nested_save_dialog_widgets, lengthof(_nested_save_dialog_widgets)
02043 );
02044 
02047 static const FileType _file_modetotype[] = {
02048   FT_SAVEGAME,  
02049   FT_SCENARIO,  
02050   FT_SAVEGAME,  
02051   FT_SCENARIO,  
02052   FT_HEIGHTMAP, 
02053   FT_SAVEGAME,  
02054 };
02055 
02056 void ShowSaveLoadDialog(SaveLoadDialogMode mode)
02057 {
02058   DeleteWindowById(WC_SAVELOAD, 0);
02059 
02060   const WindowDesc *sld;
02061   switch (mode) {
02062     case SLD_SAVE_GAME:
02063     case SLD_SAVE_SCENARIO:
02064       sld = &_save_dialog_desc; break;
02065     default:
02066       sld = &_load_dialog_desc; break;
02067   }
02068 
02069   _saveload_mode = mode;
02070   _file_to_saveload.filetype = _file_modetotype[mode];
02071 
02072   new SaveLoadWindow(sld, mode);
02073 }
02074 
02075 void RedrawAutosave()
02076 {
02077   SetWindowDirty(WC_STATUS_BAR, 0);
02078 }
02079 
02080 void SetFiosType(const byte fiostype)
02081 {
02082   switch (fiostype) {
02083     case FIOS_TYPE_FILE:
02084     case FIOS_TYPE_SCENARIO:
02085       _file_to_saveload.mode = SL_LOAD;
02086       break;
02087 
02088     case FIOS_TYPE_OLDFILE:
02089     case FIOS_TYPE_OLD_SCENARIO:
02090       _file_to_saveload.mode = SL_OLD_LOAD;
02091       break;
02092 
02093 #ifdef WITH_PNG
02094     case FIOS_TYPE_PNG:
02095       _file_to_saveload.mode = SL_PNG;
02096       break;
02097 #endif /* WITH_PNG */
02098 
02099     case FIOS_TYPE_BMP:
02100       _file_to_saveload.mode = SL_BMP;
02101       break;
02102 
02103     default:
02104       _file_to_saveload.mode = SL_INVALID;
02105       break;
02106   }
02107 }

Generated on Wed Dec 23 23:27:51 2009 for OpenTTD by  doxygen 1.5.6