# smallmap_gui.cpp

Go to the documentation of this file.

    00001 /* $Id: smallmap_gui.cpp 19104 2010-02-12 13:46:41Z 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 "clear_map.h"
    00014 #include "industry.h"
    00015 #include "station_map.h"
    00016 #include "landscape.h"
    00017 #include "window_gui.h"
    00018 #include "tree_map.h"
    00019 #include "viewport_func.h"
    00020 #include "town.h"
    00021 #include "blitter/factory.hpp"
    00022 #include "tunnelbridge_map.h"
    00023 #include "strings_func.h"
    00024 #include "core/endian_func.hpp"
    00025 #include "vehicle_base.h"
    00026 #include "sound_func.h"
    00027 #include "window_func.h"
    00028 #include "company_base.h"
    00029
    00030 #include "table/strings.h"
    00031 #include "table/sprites.h"
    00032
    00034 enum SmallMapWindowWidgets {
    00035   SM_WIDGET_CAPTION,
    00036   SM_WIDGET_MAP_BORDER,
    00037   SM_WIDGET_MAP,
    00038   SM_WIDGET_LEGEND,
    00039   SM_WIDGET_ZOOM_IN,
    00040   SM_WIDGET_ZOOM_OUT,
    00041   SM_WIDGET_CONTOUR,
    00042   SM_WIDGET_VEHICLES,
    00043   SM_WIDGET_INDUSTRIES,
    00044   SM_WIDGET_ROUTES,
    00045   SM_WIDGET_VEGETATION,
    00046   SM_WIDGET_OWNERS,
    00047   SM_WIDGET_CENTERMAP,
    00048   SM_WIDGET_TOGGLETOWNNAME,
    00049   SM_WIDGET_SELECTINDUSTRIES,
    00050   SM_WIDGET_ENABLEINDUSTRIES,
    00051   SM_WIDGET_DISABLEINDUSTRIES,
    00052   SM_WIDGET_SHOW_HEIGHT,
    00053 };
    00054
    00055 static int _smallmap_industry_count;
    00056
    00058 #define MK(a, b) {a, b, INVALID_INDUSTRYTYPE, true, false, false}
    00059
    00060 #define MC(b) MK(0, b)
    00061
    00062 #define MKEND() {0, STR_NULL, INVALID_INDUSTRYTYPE, true, true, false}
    00063
    00065 #define MS(a, b) {a, b, INVALID_INDUSTRYTYPE, true, false, true}
    00066
    00068 struct LegendAndColour {
    00069   uint8 colour;
    00070   StringID legend;
    00071   IndustryType type;
    00072   bool show_on_map;
    00073   bool end;
    00074   bool col_break;
    00075 };
    00076
    00078 static LegendAndColour _legend_land_contours[] = {
    00079   /* The colours for the following values are set at BuildLandLegend() based on each colour scheme. */
    00080   MC(STR_SMALLMAP_LEGENDA_100M),
    00081   MC(STR_SMALLMAP_LEGENDA_200M),
    00082   MC(STR_SMALLMAP_LEGENDA_300M),
    00083   MC(STR_SMALLMAP_LEGENDA_400M),
    00084   MC(STR_SMALLMAP_LEGENDA_500M),
    00085
    00086   MS(0xD7, STR_SMALLMAP_LEGENDA_ROADS),
    00087   MK(0x0A, STR_SMALLMAP_LEGENDA_RAILROADS),
    00088   MK(0x98, STR_SMALLMAP_LEGENDA_STATIONS_AIRPORTS_DOCKS),
    00089   MK(0xB5, STR_SMALLMAP_LEGENDA_BUILDINGS_INDUSTRIES),
    00090   MK(0x0F, STR_SMALLMAP_LEGENDA_VEHICLES),
    00091   MKEND()
    00092 };
    00093
    00094 static const LegendAndColour _legend_vehicles[] = {
    00095   MK(0xB8, STR_SMALLMAP_LEGENDA_TRAINS),
    00096   MK(0xBF, STR_SMALLMAP_LEGENDA_ROAD_VEHICLES),
    00097   MK(0x98, STR_SMALLMAP_LEGENDA_SHIPS),
    00098   MK(0x0F, STR_SMALLMAP_LEGENDA_AIRCRAFT),
    00099
    00100   MS(0xD7, STR_SMALLMAP_LEGENDA_TRANSPORT_ROUTES),
    00101   MK(0xB5, STR_SMALLMAP_LEGENDA_BUILDINGS_INDUSTRIES),
    00102   MKEND()
    00103 };
    00104
    00105 static const LegendAndColour _legend_routes[] = {
    00106   MK(0xD7, STR_SMALLMAP_LEGENDA_ROADS),
    00107   MK(0x0A, STR_SMALLMAP_LEGENDA_RAILROADS),
    00108   MK(0xB5, STR_SMALLMAP_LEGENDA_BUILDINGS_INDUSTRIES),
    00109
    00110   MS(0x56, STR_SMALLMAP_LEGENDA_RAILROAD_STATION),
    00111   MK(0xC2, STR_SMALLMAP_LEGENDA_TRUCK_LOADING_BAY),
    00112   MK(0xBF, STR_SMALLMAP_LEGENDA_BUS_STATION),
    00113   MK(0xB8, STR_SMALLMAP_LEGENDA_AIRPORT_HELIPORT),
    00114   MK(0x98, STR_SMALLMAP_LEGENDA_DOCK),
    00115   MKEND()
    00116 };
    00117
    00118 static const LegendAndColour _legend_vegetation[] = {
    00119   MK(0x52, STR_SMALLMAP_LEGENDA_ROUGH_LAND),
    00120   MK(0x54, STR_SMALLMAP_LEGENDA_GRASS_LAND),
    00121   MK(0x37, STR_SMALLMAP_LEGENDA_BARE_LAND),
    00122   MK(0x25, STR_SMALLMAP_LEGENDA_FIELDS),
    00123   MK(0x57, STR_SMALLMAP_LEGENDA_TREES),
    00124   MK(0xD0, STR_SMALLMAP_LEGENDA_FOREST),
    00125
    00126   MS(0x0A, STR_SMALLMAP_LEGENDA_ROCKS),
    00127   MK(0xC2, STR_SMALLMAP_LEGENDA_DESERT),
    00128   MK(0x98, STR_SMALLMAP_LEGENDA_SNOW),
    00129   MK(0xD7, STR_SMALLMAP_LEGENDA_TRANSPORT_ROUTES),
    00130   MK(0xB5, STR_SMALLMAP_LEGENDA_BUILDINGS_INDUSTRIES),
    00131   MKEND()
    00132 };
    00133
    00134 static const LegendAndColour _legend_land_owners[] = {
    00135   MK(0xCA, STR_SMALLMAP_LEGENDA_WATER),
    00136   MK(0x54, STR_SMALLMAP_LEGENDA_NO_OWNER),
    00137   MK(0xB4, STR_SMALLMAP_LEGENDA_TOWNS),
    00138   MK(0x20, STR_SMALLMAP_LEGENDA_INDUSTRIES),
    00139   MKEND()
    00140 };
    00141 #undef MK
    00142 #undef MC
    00143 #undef MS
    00144 #undef MKEND
    00145
    00148 static LegendAndColour _legend_from_industries[NUM_INDUSTRYTYPES + 1];
    00149 /* For connecting industry type to position in industries list(small map legend) */
    00150 static uint _industry_to_list_pos[NUM_INDUSTRYTYPES];
    00152 static bool _smallmap_industry_show_heightmap;
    00153
    00157 void BuildIndustriesLegend()
    00158 {
    00159   uint j = 0;
    00160
    00161   /* Add each name */
    00162   for (IndustryType i = 0; i < NUM_INDUSTRYTYPES; i++) {
    00163     const IndustrySpec *indsp = GetIndustrySpec(i);
    00164     if (indsp->enabled) {
    00165       _legend_from_industries[j].legend = indsp->name;
    00166       _legend_from_industries[j].colour = indsp->map_colour;
    00167       _legend_from_industries[j].type = i;
    00168       _legend_from_industries[j].show_on_map = true;
    00169       _legend_from_industries[j].col_break = false;
    00170       _legend_from_industries[j].end = false;
    00171
    00172       /* Store widget number for this industry type */
    00173       _industry_to_list_pos[i] = j;
    00174       j++;
    00175     }
    00176   }
    00177   /* Terminate the list */
    00178   _legend_from_industries[j].end = true;
    00179
    00180   /* Store number of enabled industries */
    00181   _smallmap_industry_count = j;
    00182 }
    00183
    00184 static const LegendAndColour * const _legend_table[] = {
    00185   _legend_land_contours,
    00186   _legend_vehicles,
    00187   _legend_from_industries,
    00188   _legend_routes,
    00189   _legend_vegetation,
    00190   _legend_land_owners,
    00191 };
    00192
    00193 #define MKCOLOUR(x) TO_LE32X(x)
    00194
    00196 static const uint32 _green_map_heights[] = {
    00197   MKCOLOUR(0x5A5A5A5A),
    00198   MKCOLOUR(0x5A5B5A5B),
    00199   MKCOLOUR(0x5B5B5B5B),
    00200   MKCOLOUR(0x5B5C5B5C),
    00201   MKCOLOUR(0x5C5C5C5C),
    00202   MKCOLOUR(0x5C5D5C5D),
    00203   MKCOLOUR(0x5D5D5D5D),
    00204   MKCOLOUR(0x5D5E5D5E),
    00205   MKCOLOUR(0x5E5E5E5E),
    00206   MKCOLOUR(0x5E5F5E5F),
    00207   MKCOLOUR(0x5F5F5F5F),
    00208   MKCOLOUR(0x5F1F5F1F),
    00209   MKCOLOUR(0x1F1F1F1F),
    00210   MKCOLOUR(0x1F271F27),
    00211   MKCOLOUR(0x27272727),
    00212   MKCOLOUR(0x27272727),
    00213 };
    00214 assert_compile(lengthof(_green_map_heights) == MAX_TILE_HEIGHT + 1);
    00215
    00217 static const uint32 _dark_green_map_heights[] = {
    00218   MKCOLOUR(0x60606060),
    00219   MKCOLOUR(0x60616061),
    00220   MKCOLOUR(0x61616161),
    00221   MKCOLOUR(0x61626162),
    00222   MKCOLOUR(0x62626262),
    00223   MKCOLOUR(0x62636263),
    00224   MKCOLOUR(0x63636363),
    00225   MKCOLOUR(0x63646364),
    00226   MKCOLOUR(0x64646464),
    00227   MKCOLOUR(0x64656465),
    00228   MKCOLOUR(0x65656565),
    00229   MKCOLOUR(0x65666566),
    00230   MKCOLOUR(0x66666666),
    00231   MKCOLOUR(0x66676667),
    00232   MKCOLOUR(0x67676767),
    00233   MKCOLOUR(0x67676767),
    00234 };
    00235 assert_compile(lengthof(_dark_green_map_heights) == MAX_TILE_HEIGHT + 1);
    00236
    00238 static const uint32 _violet_map_heights[] = {
    00239   MKCOLOUR(0x80808080),
    00240   MKCOLOUR(0x80818081),
    00241   MKCOLOUR(0x81818181),
    00242   MKCOLOUR(0x81828182),
    00243   MKCOLOUR(0x82828282),
    00244   MKCOLOUR(0x82838283),
    00245   MKCOLOUR(0x83838383),
    00246   MKCOLOUR(0x83848384),
    00247   MKCOLOUR(0x84848484),
    00248   MKCOLOUR(0x84858485),
    00249   MKCOLOUR(0x85858585),
    00250   MKCOLOUR(0x85868586),
    00251   MKCOLOUR(0x86868686),
    00252   MKCOLOUR(0x86878687),
    00253   MKCOLOUR(0x87878787),
    00254   MKCOLOUR(0x87878787),
    00255 };
    00256 assert_compile(lengthof(_violet_map_heights) == MAX_TILE_HEIGHT + 1);
    00257
    00259 struct SmallMapColourScheme {
    00260   const uint32 *height_colours;
    00261   uint32 default_colour;
    00262 };
    00263
    00265 static const SmallMapColourScheme _heightmap_schemes[] = {
    00266   {_green_map_heights,      MKCOLOUR(0x54545454)},
    00267   {_dark_green_map_heights, MKCOLOUR(0x62626262)},
    00268   {_violet_map_heights,     MKCOLOUR(0x82828282)},
    00269 };
    00270
    00271 void BuildLandLegend()
    00272 {
    00273   _legend_land_contours[0].colour = _heightmap_schemes[_settings_client.gui.smallmap_land_colour].height_colours[0];
    00274   _legend_land_contours[1].colour = _heightmap_schemes[_settings_client.gui.smallmap_land_colour].height_colours[4];
    00275   _legend_land_contours[2].colour = _heightmap_schemes[_settings_client.gui.smallmap_land_colour].height_colours[8];
    00276   _legend_land_contours[3].colour = _heightmap_schemes[_settings_client.gui.smallmap_land_colour].height_colours[12];
    00277   _legend_land_contours[4].colour = _heightmap_schemes[_settings_client.gui.smallmap_land_colour].height_colours[14];
    00278 }
    00279
    00280 struct AndOr {
    00281   uint32 mor;
    00282   uint32 mand;
    00283 };
    00284
    00285 static inline uint32 ApplyMask(uint32 colour, const AndOr *mask)
    00286 {
    00287   return (colour & mask->mand) | mask->mor;
    00288 }
    00289
    00290
    00292 static const AndOr _smallmap_contours_andor[] = {
    00293   {MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)}, // MP_CLEAR
    00294   {MKCOLOUR(0x000A0A00), MKCOLOUR(0xFF0000FF)}, // MP_RAILWAY
    00295   {MKCOLOUR(0x00D7D700), MKCOLOUR(0xFF0000FF)}, // MP_ROAD
    00296   {MKCOLOUR(0x00B5B500), MKCOLOUR(0xFF0000FF)}, // MP_HOUSE
    00297   {MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)}, // MP_TREES
    00298   {MKCOLOUR(0x98989898), MKCOLOUR(0x00000000)}, // MP_STATION
    00299   {MKCOLOUR(0xCACACACA), MKCOLOUR(0x00000000)}, // MP_WATER
    00300   {MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)}, // MP_VOID
    00301   {MKCOLOUR(0xB5B5B5B5), MKCOLOUR(0x00000000)}, // MP_INDUSTRY
    00302   {MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)}, // MP_TUNNELBRIDGE
    00303   {MKCOLOUR(0x00B5B500), MKCOLOUR(0xFF0000FF)}, // MP_UNMOVABLE
    00304   {MKCOLOUR(0x000A0A00), MKCOLOUR(0xFF0000FF)},
    00305 };
    00306
    00308 static const AndOr _smallmap_vehicles_andor[] = {
    00309   {MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)}, // MP_CLEAR
    00310   {MKCOLOUR(0x00D7D700), MKCOLOUR(0xFF0000FF)}, // MP_RAILWAY
    00311   {MKCOLOUR(0x00D7D700), MKCOLOUR(0xFF0000FF)}, // MP_ROAD
    00312   {MKCOLOUR(0x00B5B500), MKCOLOUR(0xFF0000FF)}, // MP_HOUSE
    00313   {MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)}, // MP_TREES
    00314   {MKCOLOUR(0x00D7D700), MKCOLOUR(0xFF0000FF)}, // MP_STATION
    00315   {MKCOLOUR(0xCACACACA), MKCOLOUR(0x00000000)}, // MP_WATER
    00316   {MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)}, // MP_VOID
    00317   {MKCOLOUR(0xB5B5B5B5), MKCOLOUR(0x00000000)}, // MP_INDUSTRY
    00318   {MKCOLOUR(0x00000000), MKCOLOUR(0xFFFFFFFF)}, // MP_TUNNELBRIDGE
    00319   {MKCOLOUR(0x00B5B500), MKCOLOUR(0xFF0000FF)}, // MP_UNMOVABLE
    00320   {MKCOLOUR(0x00D7D700), MKCOLOUR(0xFF0000FF)},
    00321 };
    00322
    00324 static const byte _tiletype_importance[] = {
    00325   2, // MP_CLEAR
    00326   8, // MP_RAILWAY
    00327   7, // MP_ROAD
    00328   5, // MP_HOUSE
    00329   2, // MP_TREES
    00330   9, // MP_STATION
    00331   2, // MP_WATER
    00332   1, // MP_VOID
    00333   6, // MP_INDUSTRY
    00334   8, // MP_TUNNELBRIDGE
    00335   2, // MP_UNMOVABLE
    00336   0,
    00337 };
    00338
    00339
    00340 static inline TileType GetEffectiveTileType(TileIndex tile)
    00341 {
    00342   TileType t = GetTileType(tile);
    00343
    00344   if (t == MP_TUNNELBRIDGE) {
    00345     TransportType tt = GetTunnelBridgeTransportType(tile);
    00346
    00347     switch (tt) {
    00348       case TRANSPORT_RAIL: t = MP_RAILWAY; break;
    00349       case TRANSPORT_ROAD: t = MP_ROAD;    break;
    00350       default:             t = MP_WATER;   break;
    00351     }
    00352   }
    00353   return t;
    00354 }
    00355
    00362 static inline uint32 GetSmallMapContoursPixels(TileIndex tile, TileType t)
    00363 {
    00364   const SmallMapColourScheme *cs = &_heightmap_schemes[_settings_client.gui.smallmap_land_colour];
    00365   return ApplyMask(cs->height_colours[TileHeight(tile)], &_smallmap_contours_andor[t]);
    00366 }
    00367
    00375 static inline uint32 GetSmallMapVehiclesPixels(TileIndex tile, TileType t)
    00376 {
    00377   const SmallMapColourScheme *cs = &_heightmap_schemes[_settings_client.gui.smallmap_land_colour];
    00378   return ApplyMask(cs->default_colour, &_smallmap_vehicles_andor[t]);
    00379 }
    00380
    00388 static inline uint32 GetSmallMapIndustriesPixels(TileIndex tile, TileType t)
    00389 {
    00390   if (t == MP_INDUSTRY) {
    00391     /* If industry is allowed to be seen, use its colour on the map */
    00392     if (_legend_from_industries[_industry_to_list_pos[Industry::GetByTile(tile)->type]].show_on_map) {
    00393       return GetIndustrySpec(Industry::GetByTile(tile)->type)->map_colour * 0x01010101;
    00394     } else {
    00395       /* Otherwise, return the colour of the clear tiles, which will make it disappear */
    00396       t = MP_CLEAR;
    00397     }
    00398   }
    00399
    00400   const SmallMapColourScheme *cs = &_heightmap_schemes[_settings_client.gui.smallmap_land_colour];
    00401   return ApplyMask(_smallmap_industry_show_heightmap ? cs->height_colours[TileHeight(tile)] : cs->default_colour, &_smallmap_vehicles_andor[t]);
    00402 }
    00403
    00411 static inline uint32 GetSmallMapRoutesPixels(TileIndex tile, TileType t)
    00412 {
    00413   if (t == MP_STATION) {
    00414     switch (GetStationType(tile)) {
    00415       case STATION_RAIL:    return MKCOLOUR(0x56565656);
    00416       case STATION_AIRPORT: return MKCOLOUR(0xB8B8B8B8);
    00417       case STATION_TRUCK:   return MKCOLOUR(0xC2C2C2C2);
    00418       case STATION_BUS:     return MKCOLOUR(0xBFBFBFBF);
    00419       case STATION_DOCK:    return MKCOLOUR(0x98989898);
    00420       default:              return MKCOLOUR(0xFFFFFFFF);
    00421     }
    00422   }
    00423
    00424   /* Ground colour */
    00425   const SmallMapColourScheme *cs = &_heightmap_schemes[_settings_client.gui.smallmap_land_colour];
    00426   return ApplyMask(cs->default_colour, &_smallmap_contours_andor[t]);
    00427 }
    00428
    00429
    00430 static const uint32 _vegetation_clear_bits[] = {
    00431   MKCOLOUR(0x54545454),
    00432   MKCOLOUR(0x52525252),
    00433   MKCOLOUR(0x0A0A0A0A),
    00434   MKCOLOUR(0x25252525),
    00435   MKCOLOUR(0x98989898),
    00436   MKCOLOUR(0xC2C2C2C2),
    00437   MKCOLOUR(0x54545454),
    00438   MKCOLOUR(0x54545454),
    00439 };
    00440
    00448 static inline uint32 GetSmallMapVegetationPixels(TileIndex tile, TileType t)
    00449 {
    00450   switch (t) {
    00451     case MP_CLEAR:
    00452       return (IsClearGround(tile, CLEAR_GRASS) && GetClearDensity(tile) < 3) ? MKCOLOUR(0x37373737) : _vegetation_clear_bits[GetClearGround(tile)];
    00453
    00454     case MP_INDUSTRY:
    00455       return GetIndustrySpec(Industry::GetByTile(tile)->type)->check_proc == CHECK_FOREST ? MKCOLOUR(0xD0D0D0D0) : MKCOLOUR(0xB5B5B5B5);
    00456
    00457     case MP_TREES:
    00458       if (GetTreeGround(tile) == TREE_GROUND_SNOW_DESERT || GetTreeGround(tile) == TREE_GROUND_ROUGH_SNOW) {
    00459         return (_settings_game.game_creation.landscape == LT_ARCTIC) ? MKCOLOUR(0x98575798) : MKCOLOUR(0xC25757C2);
    00460       }
    00461       return MKCOLOUR(0x54575754);
    00462
    00463     default:
    00464       return ApplyMask(MKCOLOUR(0x54545454), &_smallmap_vehicles_andor[t]);
    00465   }
    00466 }
    00467
    00468
    00469 static uint32 _owner_colours[OWNER_END + 1];
    00470
    00478 static inline uint32 GetSmallMapOwnerPixels(TileIndex tile, TileType t)
    00479 {
    00480   Owner o;
    00481
    00482   switch (t) {
    00483     case MP_INDUSTRY: o = OWNER_END;          break;
    00484     case MP_HOUSE:    o = OWNER_TOWN;         break;
    00485     default:          o = GetTileOwner(tile); break;
    00486     /* FIXME: For MP_ROAD there are multiple owners.
    00487      * GetTileOwner returns the rail owner (level crossing) resp. the owner of ROADTYPE_ROAD (normal road),
    00488      * even if there are no ROADTYPE_ROAD bits on the tile.
    00489      */
    00490   }
    00491
    00492   return _owner_colours[o];
    00493 }
    00494
    00496 static const byte _vehicle_type_colours[6] = {
    00497   184, 191, 152, 15, 215, 184
    00498 };
    00499
    00500
    00502 class SmallMapWindow : public Window {
    00504   enum SmallMapType {
    00505     SMT_CONTOUR,
    00506     SMT_VEHICLES,
    00507     SMT_INDUSTRY,
    00508     SMT_ROUTES,
    00509     SMT_VEGETATION,
    00510     SMT_OWNER,
    00511   };
    00512
    00514   enum ZoomLevelChange {
    00515     ZLC_INITIALIZE,
    00516     ZLC_ZOOM_OUT,
    00517     ZLC_ZOOM_IN,
    00518   };
    00519
    00520   static SmallMapType map_type;
    00521   static bool show_towns;
    00522
    00523   static const uint LEGEND_BLOB_WIDTH = 8;
    00524   static const uint INDUSTRY_MIN_NUMBER_OF_COLUMNS = 2;
    00525   uint min_number_of_columns;
    00526   uint min_number_of_fixed_rows;
    00527   uint column_width;
    00528
    00529   int32 scroll_x;
    00530   int32 scroll_y;
    00531   int32 subscroll;
    00532   int zoom;
    00533
    00534   static const uint8 FORCE_REFRESH_PERIOD = 0x1F;
    00535   uint8 refresh;
    00536
    00543   FORCEINLINE Point RemapTile(int tile_x, int tile_y) const
    00544   {
    00545     int x_offset = tile_x - this->scroll_x / TILE_SIZE;
    00546     int y_offset = tile_y - this->scroll_y / TILE_SIZE;
    00547
    00548     if (this->zoom == 1) return RemapCoords(x_offset, y_offset, 0);
    00549
    00550     /* For negative offsets, round towards -inf. */
    00551     if (x_offset < 0) x_offset -= this->zoom - 1;
    00552     if (y_offset < 0) y_offset -= this->zoom - 1;
    00553
    00554     return RemapCoords(x_offset / this->zoom, y_offset / this->zoom, 0);
    00555   }
    00556
    00567   FORCEINLINE Point PixelToTile(int px, int py, int *sub, bool add_sub = true) const
    00568   {
    00569     if (add_sub) px += this->subscroll;  // Total horizontal offset.
    00570
    00571     /* For each two rows down, add a x and a y tile, and
    00572      * For each four pixels to the right, move a tile to the right. */
    00573     Point pt = {((py >> 1) - (px >> 2)) * this->zoom, ((py >> 1) + (px >> 2)) * this->zoom};
    00574     px &= 3;
    00575
    00576     if (py & 1) { // Odd number of rows, handle the 2 pixel shift.
    00577       if (px < 2) {
    00578         pt.x += this->zoom;
    00579         px += 2;
    00580       } else {
    00581         pt.y += this->zoom;
    00582         px -= 2;
    00583       }
    00584     }
    00585
    00586     *sub = px;
    00587     return pt;
    00588   }
    00589
    00599   Point ComputeScroll(int tx, int ty, int x, int y, int *sub)
    00600   {
    00601     assert(x >= 0 && y >= 0);
    00602
    00603     int new_sub;
    00604     Point tile_xy = PixelToTile(x, y, &new_sub, false);
    00605     tx -= tile_xy.x;
    00606     ty -= tile_xy.y;
    00607
    00608     Point scroll;
    00609     if (new_sub == 0) {
    00610       *sub = 0;
    00611       scroll.x = (tx + this->zoom) * TILE_SIZE;
    00612       scroll.y = (ty - this->zoom) * TILE_SIZE;
    00613     } else {
    00614       *sub = 4 - new_sub;
    00615       scroll.x = (tx + 2 * this->zoom) * TILE_SIZE;
    00616       scroll.y = (ty - 2 * this->zoom) * TILE_SIZE;
    00617     }
    00618     return scroll;
    00619   }
    00620
    00626   void SetZoomLevel(ZoomLevelChange change, const Point *zoom_pt)
    00627   {
    00628     static const int zoomlevels[] = {1, 2, 4, 6, 8}; // Available zoom levels. Bigger number means more zoom-out (further away).
    00629     static const int MIN_ZOOM_INDEX = 0;
    00630     static const int MAX_ZOOM_INDEX = lengthof(zoomlevels) - 1;
    00631
    00632     int new_index, cur_index, sub;
    00633     Point tile;
    00634     switch (change) {
    00635       case ZLC_INITIALIZE:
    00636         cur_index = - 1; // Definitely different from new_index.
    00637         new_index = MIN_ZOOM_INDEX;
    00638         break;
    00639
    00640       case ZLC_ZOOM_IN:
    00641       case ZLC_ZOOM_OUT:
    00642         for (cur_index = MIN_ZOOM_INDEX; cur_index <= MAX_ZOOM_INDEX; cur_index++) {
    00643           if (this->zoom == zoomlevels[cur_index]) break;
    00644         }
    00645         assert(cur_index <= MAX_ZOOM_INDEX);
    00646
    00647         tile = this->PixelToTile(zoom_pt->x, zoom_pt->y, &sub);
    00648         new_index = Clamp(cur_index + ((change == ZLC_ZOOM_IN) ? -1 : 1), MIN_ZOOM_INDEX, MAX_ZOOM_INDEX);
    00649         break;
    00650
    00651       default: NOT_REACHED();
    00652     }
    00653
    00654     if (new_index != cur_index) {
    00655       this->zoom = zoomlevels[new_index];
    00656       if (cur_index >= 0) {
    00657         Point new_tile = this->PixelToTile(zoom_pt->x, zoom_pt->y, &sub);
    00658         this->SetNewScroll(this->scroll_x + (tile.x - new_tile.x) * TILE_SIZE,
    00659             this->scroll_y + (tile.y - new_tile.y) * TILE_SIZE, sub);
    00660       }
    00661       this->SetWidgetDisabledState(SM_WIDGET_ZOOM_IN,  this->zoom == zoomlevels[MIN_ZOOM_INDEX]);
    00662       this->SetWidgetDisabledState(SM_WIDGET_ZOOM_OUT, this->zoom == zoomlevels[MAX_ZOOM_INDEX]);
    00663       this->SetDirty();
    00664     }
    00665   }
    00666
    00672   inline uint32 GetTileColours(const TileArea &ta) const
    00673   {
    00674     int importance = 0;
    00675     TileIndex tile = INVALID_TILE; // Position of the most important tile.
    00676     TileType et = MP_VOID;         // Effective tile type at that position.
    00677
    00678     TILE_AREA_LOOP(ti, ta) {
    00679       TileType ttype = GetEffectiveTileType(ti);
    00680       if (_tiletype_importance[ttype] > importance) {
    00681         importance = _tiletype_importance[ttype];
    00682         tile = ti;
    00683         et = ttype;
    00684       }
    00685     }
    00686
    00687     switch (this->map_type) {
    00688       case SMT_CONTOUR:
    00689         return GetSmallMapContoursPixels(tile, et);
    00690
    00691       case SMT_VEHICLES:
    00692         return GetSmallMapVehiclesPixels(tile, et);
    00693
    00694       case SMT_INDUSTRY:
    00695         return GetSmallMapIndustriesPixels(tile, et);
    00696
    00697       case SMT_ROUTES:
    00698         return GetSmallMapRoutesPixels(tile, et);
    00699
    00700       case SMT_VEGETATION:
    00701         return GetSmallMapVegetationPixels(tile, et);
    00702
    00703       case SMT_OWNER:
    00704         return GetSmallMapOwnerPixels(tile, et);
    00705
    00706       default: NOT_REACHED();
    00707     }
    00708   }
    00709
    00724   void DrawSmallMapColumn(void *dst, uint xc, uint yc, int pitch, int reps, int start_pos, int end_pos, Blitter *blitter) const
    00725   {
    00726     void *dst_ptr_abs_end = blitter->MoveTo(_screen.dst_ptr, 0, _screen.height);
    00727     uint min_xy = _settings_game.construction.freeform_edges ? 1 : 0;
    00728
    00729     do {
    00730       /* Check if the tile (xc,yc) is within the map range */
    00731       if (xc >= MapMaxX() || yc >= MapMaxY()) continue;
    00732
    00733       /* Check if the dst pointer points to a pixel inside the screen buffer */
    00734       if (dst < _screen.dst_ptr) continue;
    00735       if (dst >= dst_ptr_abs_end) continue;
    00736
    00737       /* Construct tilearea covered by (xc, yc, xc + this->zoom, yc + this->zoom) such that it is within min_xy limits. */
    00738       TileArea ta;
    00739       if (min_xy == 1 && (xc == 0 || yc == 0)) {
    00740         if (this->zoom == 1) continue; // The tile area is empty, don't draw anything.
    00741
    00742         ta = TileArea(TileXY(max(min_xy, xc), max(min_xy, yc)), this->zoom - (xc == 0), this->zoom - (yc == 0));
    00743       } else {
    00744         ta = TileArea(TileXY(xc, yc), this->zoom, this->zoom);
    00745       }
    00746       ta.ClampToMap(); // Clamp to map boundaries (may contain MP_VOID tiles!).
    00747
    00748       uint32 val = this->GetTileColours(ta);
    00749       uint8 *val8 = (uint8 *)&val;
    00750       int idx = max(0, -start_pos);
    00751       for (int pos = max(0, start_pos); pos < end_pos; pos++) {
    00752         blitter->SetPixel(dst, idx, 0, val8[idx]);
    00753         idx++;
    00754       }
    00755     /* Switch to next tile in the column */
    00756     } while (xc += this->zoom, yc += this->zoom, dst = blitter->MoveTo(dst, pitch, 0), --reps != 0);
    00757   }
    00758
    00764   void DrawVehicles(const DrawPixelInfo *dpi, Blitter *blitter) const
    00765   {
    00766     const Vehicle *v;
    00767     FOR_ALL_VEHICLES(v) {
    00768       if (v->type == VEH_EFFECT) continue;
    00769       if (v->vehstatus & (VS_HIDDEN | VS_UNCLICKABLE)) continue;
    00770
    00771       /* Remap into flat coordinates. */
    00772       Point pt = this->RemapTile(v->x_pos / TILE_SIZE, v->y_pos / TILE_SIZE);
    00773
    00774       int y = pt.y - dpi->top;
    00775       if (!IsInsideMM(y, 0, dpi->height)) continue; // y is out of bounds.
    00776
    00777       bool skip = false; // Default is to draw both pixels.
    00778       int x = pt.x - this->subscroll - 3 - dpi->left; // Offset X coordinate.
    00779       if (x < 0) {
    00780         /* if x+1 is 0, that means we're on the very left edge,
    00781          * and should thus only draw a single pixel */
    00782         if (++x != 0) continue;
    00783         skip = true;
    00784       } else if (x >= dpi->width - 1) {
    00785         /* Check if we're at the very right edge, and if so draw only a single pixel */
    00786         if (x != dpi->width - 1) continue;
    00787         skip = true;
    00788       }
    00789
    00790       /* Calculate pointer to pixel and the colour */
    00791       byte colour = (this->map_type == SMT_VEHICLES) ? _vehicle_type_colours[v->type] : 0xF;
    00792
    00793       /* And draw either one or two pixels depending on clipping */
    00794       blitter->SetPixel(dpi->dst_ptr, x, y, colour);
    00795       if (!skip) blitter->SetPixel(dpi->dst_ptr, x + 1, y, colour);
    00796     }
    00797   }
    00798
    00803   void DrawTowns(const DrawPixelInfo *dpi) const
    00804   {
    00805     const Town *t;
    00806     FOR_ALL_TOWNS(t) {
    00807       /* Remap the town coordinate */
    00808       Point pt = this->RemapTile(TileX(t->xy), TileY(t->xy));
    00809       int x = pt.x - this->subscroll - (t->sign.width_small >> 1);
    00810       int y = pt.y;
    00811
    00812       /* Check if the town sign is within bounds */
    00813       if (x + t->sign.width_small > dpi->left &&
    00814           x < dpi->left + dpi->width &&
    00815           y + FONT_HEIGHT_SMALL > dpi->top &&
    00816           y < dpi->top + dpi->height) {
    00817         /* And draw it. */
    00818         SetDParam(0, t->index);
    00819         DrawString(x, x + t->sign.width_small, y, STR_SMALLMAP_TOWN);
    00820       }
    00821     }
    00822   }
    00823
    00830   static inline void DrawVertMapIndicator(int x, int y, int y2)
    00831   {
    00832     GfxFillRect(x, y,      x, y + 3, 69);
    00833     GfxFillRect(x, y2 - 3, x, y2,    69);
    00834   }
    00835
    00842   static inline void DrawHorizMapIndicator(int x, int x2, int y)
    00843   {
    00844     GfxFillRect(x,      y, x + 3, y, 69);
    00845     GfxFillRect(x2 - 3, y, x2,    y, 69);
    00846   }
    00847
    00851   void DrawMapIndicators() const
    00852   {
    00853     /* Find main viewport. */
    00854     const ViewPort *vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport;
    00855
    00856     Point tile = InverseRemapCoords(vp->virtual_left, vp->virtual_top);
    00857     Point tl = this->RemapTile(tile.x >> 4, tile.y >> 4);
    00858     tl.x -= this->subscroll;
    00859
    00860     tile = InverseRemapCoords(vp->virtual_left + vp->virtual_width, vp->virtual_top + vp->virtual_height);
    00861     Point br = this->RemapTile(tile.x >> 4, tile.y >> 4);
    00862     br.x -= this->subscroll;
    00863
    00864     SmallMapWindow::DrawVertMapIndicator(tl.x, tl.y, br.y);
    00865     SmallMapWindow::DrawVertMapIndicator(br.x, tl.y, br.y);
    00866
    00867     SmallMapWindow::DrawHorizMapIndicator(tl.x, br.x, tl.y);
    00868     SmallMapWindow::DrawHorizMapIndicator(tl.x, br.x, br.y);
    00869   }
    00870
    00882   void DrawSmallMap(DrawPixelInfo *dpi) const
    00883   {
    00884     Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
    00885     DrawPixelInfo *old_dpi;
    00886
    00887     old_dpi = _cur_dpi;
    00888     _cur_dpi = dpi;
    00889
    00890     /* Clear it */
    00891     GfxFillRect(dpi->left, dpi->top, dpi->left + dpi->width - 1, dpi->top + dpi->height - 1, 0);
    00892
    00893     /* Setup owner table */
    00894     if (this->map_type == SMT_OWNER) {
    00895       const Company *c;
    00896
    00897       /* Fill with some special colours */
    00898       _owner_colours[OWNER_TOWN]  = MKCOLOUR(0xB4B4B4B4);
    00899       _owner_colours[OWNER_NONE]  = _heightmap_schemes[_settings_client.gui.smallmap_land_colour].default_colour;
    00900       _owner_colours[OWNER_WATER] = MKCOLOUR(0xCACACACA);
    00901       _owner_colours[OWNER_END]   = MKCOLOUR(0x20202020); // Industry
    00902
    00903       /* Now fill with the company colours */
    00904       FOR_ALL_COMPANIES(c) {
    00905         _owner_colours[c->index] = _colour_gradient[c->colour][5] * 0x01010101;
    00906       }
    00907     }
    00908
    00909     /* Which tile is displayed at (dpi->left, dpi->top)? */
    00910     int dx;
    00911     Point tile = this->PixelToTile(dpi->left, dpi->top, &dx);
    00912     int tile_x = this->scroll_x / TILE_SIZE + tile.x;
    00913     int tile_y = this->scroll_y / TILE_SIZE + tile.y;
    00914
    00915     void *ptr = blitter->MoveTo(dpi->dst_ptr, -dx - 4, 0);
    00916     int x = - dx - 4;
    00917     int y = 0;
    00918
    00919     for (;;) {
    00920       /* Distance from left edge */
    00921       if (x >= -3) {
    00922         if (x >= dpi->width) break; // Exit the loop.
    00923
    00924         int end_pos = min(dpi->width, x + 4);
    00925         int reps = (dpi->height - y + 1) / 2; // Number of lines.
    00926         if (reps > 0) {
    00927           this->DrawSmallMapColumn(ptr, tile_x, tile_y, dpi->pitch * 2, reps, x, end_pos, blitter);
    00928         }
    00929       }
    00930
    00931       if (y == 0) {
    00932         tile_y += this->zoom;
    00933         y++;
    00934         ptr = blitter->MoveTo(ptr, 0, 1);
    00935       } else {
    00936         tile_x -= this->zoom;
    00937         y--;
    00938         ptr = blitter->MoveTo(ptr, 0, -1);
    00939       }
    00940       ptr = blitter->MoveTo(ptr, 2, 0);
    00941       x += 2;
    00942     }
    00943
    00944     /* Draw vehicles */
    00945     if (this->map_type == SMT_CONTOUR || this->map_type == SMT_VEHICLES) this->DrawVehicles(dpi, blitter);
    00946
    00947     /* Draw town names */
    00948     if (this->show_towns) this->DrawTowns(dpi);
    00949
    00950     /* Draw map indicators */
    00951     this->DrawMapIndicators();
    00952
    00953     _cur_dpi = old_dpi;
    00954   }
    00955
    00956 public:
    00957   SmallMapWindow(const WindowDesc *desc, int window_number) : Window(), refresh(FORCE_REFRESH_PERIOD)
    00958   {
    00959     this->InitNested(desc, window_number);
    00960     this->LowerWidget(this->map_type + SM_WIDGET_CONTOUR);
    00961
    00962     _smallmap_industry_show_heightmap = false;
    00963     BuildLandLegend();
    00964     this->SetWidgetLoweredState(SM_WIDGET_SHOW_HEIGHT, _smallmap_industry_show_heightmap);
    00965
    00966     this->SetWidgetLoweredState(SM_WIDGET_TOGGLETOWNNAME, this->show_towns);
    00967     this->GetWidget<NWidgetStacked>(SM_WIDGET_SELECTINDUSTRIES)->SetDisplayedPlane(this->map_type != SMT_INDUSTRY);
    00968
    00969     this->SetZoomLevel(ZLC_INITIALIZE, NULL);
    00970     this->SmallMapCenterOnCurrentPos();
    00971   }
    00972
    00976   inline uint GetMaxLegendHeight() const
    00977   {
    00978     uint num_rows = max(this->min_number_of_fixed_rows, (_smallmap_industry_count + this->min_number_of_columns - 1) / this->min_number_of_columns);
    00979     return WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + num_rows * FONT_HEIGHT_SMALL;
    00980   }
    00981
    00985   inline uint GetMinLegendWidth() const
    00986   {
    00987     return WD_FRAMERECT_LEFT + this->min_number_of_columns * this->column_width;
    00988   }
    00989
    00993   inline uint GetNumberColumnsLegend(uint width) const
    00994   {
    00995     return width / this->column_width;
    00996   }
    00997
    01001   uint GetLegendHeight(uint width) const
    01002   {
    01003     uint num_columns = this->GetNumberColumnsLegend(width);
    01004     uint num_rows = max(this->min_number_of_fixed_rows, (_smallmap_industry_count + num_columns - 1) / num_columns);
    01005     return WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + num_rows * FONT_HEIGHT_SMALL;
    01006   }
    01007
    01008   virtual void SetStringParameters(int widget) const
    01009   {
    01010     switch (widget) {
    01011       case SM_WIDGET_CAPTION:
    01012         SetDParam(0, STR_SMALLMAP_TYPE_CONTOURS + this->map_type);
    01013         break;
    01014     }
    01015   }
    01016
    01017   virtual void OnInit()
    01018   {
    01019     uint min_width = 0;
    01020     this->min_number_of_columns = INDUSTRY_MIN_NUMBER_OF_COLUMNS;
    01021     this->min_number_of_fixed_rows = 0;
    01022     for (uint i = 0; i < lengthof(_legend_table); i++) {
    01023       uint height = 0;
    01024       uint num_columns = 1;
    01025       for (const LegendAndColour *tbl = _legend_table[i]; !tbl->end; ++tbl) {
    01026         StringID str;
    01027         if (i == SMT_INDUSTRY) {
    01028           SetDParam(0, tbl->legend);
    01029           SetDParam(1, IndustryPool::MAX_SIZE);
    01030           str = STR_SMALLMAP_INDUSTRY;
    01031         } else {
    01032           if (tbl->col_break) {
    01033             this->min_number_of_fixed_rows = max(this->min_number_of_fixed_rows, height);
    01034             height = 0;
    01035             num_columns++;
    01036           }
    01037           height++;
    01038           str = tbl->legend;
    01039         }
    01040         min_width = max(GetStringBoundingBox(str).width, min_width);
    01041       }
    01042       this->min_number_of_fixed_rows = max(this->min_number_of_fixed_rows, height);
    01043       this->min_number_of_columns = max(this->min_number_of_columns, num_columns);
    01044     }
    01045
    01046     /* The width of a column is the minimum width of all texts + the size of the blob + some spacing */
    01047     this->column_width = min_width + LEGEND_BLOB_WIDTH + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
    01048   }
    01049
    01050   virtual void DrawWidget(const Rect &r, int widget) const
    01051   {
    01052     switch (widget) {
    01053       case SM_WIDGET_MAP: {
    01054         DrawPixelInfo new_dpi;
    01055         if (!FillDrawPixelInfo(&new_dpi, r.left + 1, r.top + 1, r.right - r.left - 1, r.bottom - r.top - 1)) return;
    01056         this->DrawSmallMap(&new_dpi);
    01057       } break;
    01058
    01059       case SM_WIDGET_LEGEND: {
    01060         uint columns = this->GetNumberColumnsLegend(r.right - r.left + 1);
    01061         uint number_of_rows = max(this->map_type == SMT_INDUSTRY ? (_smallmap_industry_count + columns - 1) / columns : 0, this->min_number_of_fixed_rows);
    01062         bool rtl = _dynlang.text_dir == TD_RTL;
    01063         uint y_org = r.top + WD_FRAMERECT_TOP;
    01064         uint x = rtl ? r.right - this->column_width - WD_FRAMERECT_RIGHT : r.left + WD_FRAMERECT_LEFT;
    01065         uint y = y_org;
    01066         uint i = 0; // Row counter for industry legend.
    01067         uint row_height = FONT_HEIGHT_SMALL;
    01068
    01069         uint text_left  = rtl ? 0 : LEGEND_BLOB_WIDTH + WD_FRAMERECT_LEFT;
    01070         uint text_right = this->column_width - 1 - (rtl ? LEGEND_BLOB_WIDTH + WD_FRAMERECT_RIGHT : 0);
    01071         uint blob_left  = rtl ? this->column_width - 1 - LEGEND_BLOB_WIDTH : 0;
    01072         uint blob_right = rtl ? this->column_width - 1 : LEGEND_BLOB_WIDTH;
    01073
    01074         for (const LegendAndColour *tbl = _legend_table[this->map_type]; !tbl->end; ++tbl) {
    01075           if (tbl->col_break || (this->map_type == SMT_INDUSTRY && i++ >= number_of_rows)) {
    01076             /* Column break needed, continue at top, COLUMN_WIDTH pixels
    01077              * (one "row") to the right. */
    01078             x += rtl ? -(int)this->column_width : this->column_width;
    01079             y = y_org;
    01080             i = 1;
    01081           }
    01082
    01083           if (this->map_type == SMT_INDUSTRY) {
    01084             /* Industry name must be formatted, since it's not in tiny font in the specs.
    01085              * So, draw with a parameter and use the STR_SMALLMAP_INDUSTRY string, which is tiny font */
    01086             SetDParam(0, tbl->legend);
    01087             assert(tbl->type < NUM_INDUSTRYTYPES);
    01088             SetDParam(1, _industry_counts[tbl->type]);
    01089             if (!tbl->show_on_map) {
    01090               /* Simply draw the string, not the black border of the legend colour.
    01091                * This will enforce the idea of the disabled item */
    01092               DrawString(x + text_left, x + text_right, y, STR_SMALLMAP_INDUSTRY, TC_GREY);
    01093             } else {
    01094               DrawString(x + text_left, x + text_right, y, STR_SMALLMAP_INDUSTRY, TC_BLACK);
    01095               GfxFillRect(x + blob_left, y + 1, x + blob_right, y + row_height - 1, 0); // Outer border of the legend colour
    01096             }
    01097           } else {
    01098             /* Anything that is not an industry is using normal process */
    01099             GfxFillRect(x + blob_left, y + 1, x + blob_right, y + row_height - 1, 0);
    01100             DrawString(x + text_left, x + text_right, y, tbl->legend);
    01101           }
    01102           GfxFillRect(x + blob_left + 1, y + 2, x + blob_right - 1, y + row_height - 2, tbl->colour); // Legend colour
    01103
    01104           y += row_height;
    01105         }
    01106       }
    01107     }
    01108   }
    01109
    01110   virtual void OnPaint()
    01111   {
    01112     this->DrawWidgets();
    01113   }
    01114
    01115   virtual void OnClick(Point pt, int widget, int click_count)
    01116   {
    01117     switch (widget) {
    01118       case SM_WIDGET_MAP: { // Map window
    01119         /*
    01120          * XXX: scrolling with the left mouse button is done by subsequently
    01121          * clicking with the left mouse button; clicking once centers the
    01122          * large map at the selected point. So by unclicking the left mouse
    01123          * button here, it gets reclicked during the next inputloop, which
    01124          * would make it look like the mouse is being dragged, while it is
    01125          * actually being (virtually) clicked every inputloop.
    01126          */
    01127         _left_button_clicked = false;
    01128
    01129         const NWidgetBase *wid = this->GetWidget<NWidgetBase>(SM_WIDGET_MAP);
    01130         Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
    01131         int sub;
    01132         pt = this->PixelToTile(pt.x - wid->pos_x, pt.y - wid->pos_y, &sub);
    01133         pt = RemapCoords(this->scroll_x + pt.x * TILE_SIZE + this->zoom * (TILE_SIZE - sub * TILE_SIZE / 4),
    01134             this->scroll_y + pt.y * TILE_SIZE + sub * this->zoom * TILE_SIZE / 4, 0);
    01135
    01136         w->viewport->follow_vehicle = INVALID_VEHICLE;
    01137         w->viewport->dest_scrollpos_x = pt.x - (w->viewport->virtual_width  >> 1);
    01138         w->viewport->dest_scrollpos_y = pt.y - (w->viewport->virtual_height >> 1);
    01139
    01140         this->SetDirty();
    01141       } break;
    01142
    01143       case SM_WIDGET_ZOOM_IN:
    01144       case SM_WIDGET_ZOOM_OUT: {
    01145         const NWidgetBase *wid = this->GetWidget<NWidgetBase>(SM_WIDGET_MAP);
    01146         Point pt = {wid->current_x / 2, wid->current_y / 2};
    01147         this->SetZoomLevel((widget == SM_WIDGET_ZOOM_IN) ? ZLC_ZOOM_IN : ZLC_ZOOM_OUT, &pt);
    01148         SndPlayFx(SND_15_BEEP);
    01149         break;
    01150       }
    01151
    01152       case SM_WIDGET_CONTOUR:    // Show land contours
    01153       case SM_WIDGET_VEHICLES:   // Show vehicles
    01154       case SM_WIDGET_INDUSTRIES: // Show industries
    01155       case SM_WIDGET_ROUTES:     // Show transport routes
    01156       case SM_WIDGET_VEGETATION: // Show vegetation
    01157       case SM_WIDGET_OWNERS:     // Show land owners
    01158         this->RaiseWidget(this->map_type + SM_WIDGET_CONTOUR);
    01159         this->map_type = (SmallMapType)(widget - SM_WIDGET_CONTOUR);
    01160         this->LowerWidget(this->map_type + SM_WIDGET_CONTOUR);
    01161
    01162         /* Hide Enable all/Disable all buttons if is not industry type small map */
    01163         this->GetWidget<NWidgetStacked>(SM_WIDGET_SELECTINDUSTRIES)->SetDisplayedPlane(this->map_type != SMT_INDUSTRY);
    01164
    01165         this->SetDirty();
    01166         SndPlayFx(SND_15_BEEP);
    01167         break;
    01168
    01169       case SM_WIDGET_CENTERMAP: // Center the smallmap again
    01170         this->SmallMapCenterOnCurrentPos();
    01171         this->HandleButtonClick(SM_WIDGET_CENTERMAP);
    01172         SndPlayFx(SND_15_BEEP);
    01173         break;
    01174
    01175       case SM_WIDGET_TOGGLETOWNNAME: // Toggle town names
    01176         this->show_towns = !this->show_towns;
    01177         this->SetWidgetLoweredState(SM_WIDGET_TOGGLETOWNNAME, this->show_towns);
    01178
    01179         this->SetDirty();
    01180         SndPlayFx(SND_15_BEEP);
    01181         break;
    01182
    01183       case SM_WIDGET_LEGEND: // Legend
    01184         /* If industry type small map*/
    01185         if (this->map_type == SMT_INDUSTRY) {
    01186           /* If click on industries label, find right industry type and enable/disable it */
    01187           const NWidgetBase *wi = this->GetWidget<NWidgetBase>(SM_WIDGET_LEGEND); // Label panel
    01188           uint line = (pt.y - wi->pos_y - WD_FRAMERECT_TOP) / FONT_HEIGHT_SMALL;
    01189           uint columns = this->GetNumberColumnsLegend(wi->current_x);
    01190           uint number_of_rows = max((_smallmap_industry_count + columns - 1) / columns, this->min_number_of_fixed_rows);
    01191           if (line >= number_of_rows) break;
    01192
    01193           bool rtl = _dynlang.text_dir == TD_RTL;
    01194           int x = pt.x - wi->pos_x;
    01195           if (rtl) x = wi->current_x - x;
    01196           uint column = (x - WD_FRAMERECT_LEFT) / this->column_width;
    01197
    01198           /* Check if click is on industry label*/
    01199           int industry_pos = (column * number_of_rows) + line;
    01200           if (industry_pos < _smallmap_industry_count) {
    01201             _legend_from_industries[industry_pos].show_on_map = !_legend_from_industries[industry_pos].show_on_map;
    01202           }
    01203
    01204           /* Raise the two buttons "all", as we have done a specific choice */
    01205           this->RaiseWidget(SM_WIDGET_ENABLEINDUSTRIES);
    01206           this->RaiseWidget(SM_WIDGET_DISABLEINDUSTRIES);
    01207           this->SetDirty();
    01208         }
    01209         break;
    01210
    01211       case SM_WIDGET_ENABLEINDUSTRIES: // Enable all industries
    01212         for (int i = 0; i != _smallmap_industry_count; i++) {
    01213           _legend_from_industries[i].show_on_map = true;
    01214         }
    01215         /* Toggle appeareance indicating the choice */
    01216         this->LowerWidget(SM_WIDGET_ENABLEINDUSTRIES);
    01217         this->RaiseWidget(SM_WIDGET_DISABLEINDUSTRIES);
    01218         this->SetDirty();
    01219         break;
    01220
    01221       case SM_WIDGET_DISABLEINDUSTRIES: // Disable all industries
    01222         for (int i = 0; i != _smallmap_industry_count; i++) {
    01223           _legend_from_industries[i].show_on_map = false;
    01224         }
    01225         /* Toggle appeareance indicating the choice */
    01226         this->RaiseWidget(SM_WIDGET_ENABLEINDUSTRIES);
    01227         this->LowerWidget(SM_WIDGET_DISABLEINDUSTRIES);
    01228         this->SetDirty();
    01229         break;
    01230
    01231       case SM_WIDGET_SHOW_HEIGHT: // Enable/disable showing of heightmap.
    01232         _smallmap_industry_show_heightmap = !_smallmap_industry_show_heightmap;
    01233         this->SetWidgetLoweredState(SM_WIDGET_SHOW_HEIGHT, _smallmap_industry_show_heightmap);
    01234         this->SetDirty();
    01235         break;
    01236     }
    01237   }
    01238
    01239   virtual void OnRightClick(Point pt, int widget)
    01240   {
    01241     if (widget == SM_WIDGET_MAP) {
    01242       if (_scrolling_viewport) return;
    01243       _scrolling_viewport = true;
    01244     }
    01245   }
    01246
    01247   virtual void OnMouseWheel(int wheel)
    01248   {
    01249     const NWidgetBase *wid = this->GetWidget<NWidgetBase>(SM_WIDGET_MAP);
    01250     int cursor_x = _cursor.pos.x - this->left - wid->pos_x;
    01251     int cursor_y = _cursor.pos.y - this->top  - wid->pos_y;
    01252     if (IsInsideMM(cursor_x, 0, wid->current_x) && IsInsideMM(cursor_y, 0, wid->current_y)) {
    01253       Point pt = {cursor_x, cursor_y};
    01254       this->SetZoomLevel((wheel < 0) ? ZLC_ZOOM_IN : ZLC_ZOOM_OUT, &pt);
    01255     }
    01256   }
    01257
    01258   virtual void OnTick()
    01259   {
    01260     /* Update the window every now and then */
    01261     if (--this->refresh != 0) return;
    01262
    01263     this->refresh = FORCE_REFRESH_PERIOD;
    01264     this->SetDirty();
    01265   }
    01266
    01274   void SetNewScroll(int sx, int sy, int sub)
    01275   {
    01276     const NWidgetBase *wi = this->GetWidget<NWidgetBase>(SM_WIDGET_MAP);
    01277     Point hv = InverseRemapCoords(wi->current_x * TILE_SIZE / 2, wi->current_y * TILE_SIZE / 2);
    01278     hv.x *= this->zoom;
    01279     hv.y *= this->zoom;
    01280
    01281     if (sx < -hv.x) {
    01282       sx = -hv.x;
    01283       sub = 0;
    01284     }
    01285     if (sx > (int)MapMaxX() * TILE_SIZE - hv.x) {
    01286       sx = MapMaxX() * TILE_SIZE - hv.x;
    01287       sub = 0;
    01288     }
    01289     if (sy < -hv.y) {
    01290       sy = -hv.y;
    01291       sub = 0;
    01292     }
    01293     if (sy > (int)MapMaxY() * TILE_SIZE - hv.y) {
    01294       sy = MapMaxY() * TILE_SIZE - hv.y;
    01295       sub = 0;
    01296     }
    01297
    01298     this->scroll_x = sx;
    01299     this->scroll_y = sy;
    01300     this->subscroll = sub;
    01301   }
    01302
    01303   virtual void OnScroll(Point delta)
    01304   {
    01305     _cursor.fix_at = true;
    01306
    01307     /* While tile is at (delta.x, delta.y)? */
    01308     int sub;
    01309     Point pt = this->PixelToTile(delta.x, delta.y, &sub);
    01310     this->SetNewScroll(this->scroll_x + pt.x * TILE_SIZE, this->scroll_y + pt.y * TILE_SIZE, sub);
    01311
    01312     this->SetDirty();
    01313   }
    01314
    01315   void SmallMapCenterOnCurrentPos()
    01316   {
    01317     const ViewPort *vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport;
    01318     Point pt = InverseRemapCoords(vp->virtual_left + vp->virtual_width  / 2, vp->virtual_top  + vp->virtual_height / 2);
    01319
    01320     int sub;
    01321     const NWidgetBase *wid = this->GetWidget<NWidgetBase>(SM_WIDGET_MAP);
    01322     Point sxy = this->ComputeScroll(pt.x / TILE_SIZE, pt.y / TILE_SIZE, max(0, (int)wid->current_x / 2 - 2), wid->current_y / 2, &sub);
    01323     this->SetNewScroll(sxy.x, sxy.y, sub);
    01324     this->SetDirty();
    01325   }
    01326 };
    01327
    01328 SmallMapWindow::SmallMapType SmallMapWindow::map_type = SMT_CONTOUR;
    01329 bool SmallMapWindow::show_towns = true;
    01330
    01339 class NWidgetSmallmapDisplay : public NWidgetContainer {
    01340   const SmallMapWindow *smallmap_window;
    01341 public:
    01342   NWidgetSmallmapDisplay() : NWidgetContainer(NWID_VERTICAL)
    01343   {
    01344     this->smallmap_window = NULL;
    01345   }
    01346
    01347   virtual void SetupSmallestSize(Window *w, bool init_array)
    01348   {
    01349     NWidgetBase *display = this->head;
    01350     NWidgetBase *bar = display->next;
    01351
    01352     display->SetupSmallestSize(w, init_array);
    01353     bar->SetupSmallestSize(w, init_array);
    01354
    01355     this->smallmap_window = dynamic_cast<SmallMapWindow *>(w);
    01356     this->smallest_x = max(display->smallest_x, bar->smallest_x + smallmap_window->GetMinLegendWidth());
    01357     this->smallest_y = display->smallest_y + max(bar->smallest_y, smallmap_window->GetMaxLegendHeight());
    01358     this->fill_x = max(display->fill_x, bar->fill_x);
    01359     this->fill_y = (display->fill_y == 0 && bar->fill_y == 0) ? 0 : min(display->fill_y, bar->fill_y);
    01360     this->resize_x = max(display->resize_x, bar->resize_x);
    01361     this->resize_y = min(display->resize_y, bar->resize_y);
    01362   }
    01363
    01364   virtual void AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
    01365   {
    01366     this->pos_x = x;
    01367     this->pos_y = y;
    01368     this->current_x = given_width;
    01369     this->current_y = given_height;
    01370
    01371     NWidgetBase *display = this->head;
    01372     NWidgetBase *bar = display->next;
    01373
    01374     if (sizing == ST_SMALLEST) {
    01375       this->smallest_x = given_width;
    01376       this->smallest_y = given_height;
    01377       /* Make display and bar exactly equal to their minimal size. */
    01378       display->AssignSizePosition(ST_SMALLEST, x, y, display->smallest_x, display->smallest_y, rtl);
    01379       bar->AssignSizePosition(ST_SMALLEST, x, y + display->smallest_y, bar->smallest_x, bar->smallest_y, rtl);
    01380     }
    01381
    01382     uint bar_height = max(bar->smallest_y, this->smallmap_window->GetLegendHeight(given_width - bar->smallest_x));
    01383     uint display_height = given_height - bar_height;
    01384     display->AssignSizePosition(ST_RESIZE, x, y, given_width, display_height, rtl);
    01385     bar->AssignSizePosition(ST_RESIZE, x, y + display_height, given_width, bar_height, rtl);
    01386   }
    01387
    01388   virtual NWidgetCore *GetWidgetFromPos(int x, int y)
    01389   {
    01390     if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return NULL;
    01391     for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
    01392       NWidgetCore *widget = child_wid->GetWidgetFromPos(x, y);
    01393       if (widget != NULL) return widget;
    01394     }
    01395     return NULL;
    01396   }
    01397
    01398   virtual void Draw(const Window *w)
    01399   {
    01400     for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) child_wid->Draw(w);
    01401   }
    01402 };
    01403
    01405 static const NWidgetPart _nested_smallmap_display[] = {
    01406   NWidget(WWT_PANEL, COLOUR_BROWN, SM_WIDGET_MAP_BORDER),
    01407     NWidget(WWT_INSET, COLOUR_BROWN, SM_WIDGET_MAP), SetMinimalSize(346, 140), SetResize(1, 1), SetPadding(2, 2, 2, 2), EndContainer(),
    01408   EndContainer(),
    01409 };
    01410
    01412 static const NWidgetPart _nested_smallmap_bar[] = {
    01413   NWidget(WWT_PANEL, COLOUR_BROWN),
    01414     NWidget(NWID_HORIZONTAL),
    01415       NWidget(WWT_EMPTY, INVALID_COLOUR, SM_WIDGET_LEGEND), SetResize(1, 1),
    01416       NWidget(NWID_VERTICAL),
    01417         /* Top button row. */
    01418         NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
    01419           NWidget(WWT_PUSHIMGBTN, COLOUR_BROWN, SM_WIDGET_ZOOM_IN), SetDataTip(SPR_IMG_ZOOMIN, STR_TOOLBAR_TOOLTIP_ZOOM_THE_VIEW_IN),
    01420           NWidget(WWT_PUSHIMGBTN, COLOUR_BROWN, SM_WIDGET_CENTERMAP), SetDataTip(SPR_IMG_SMALLMAP, STR_SMALLMAP_CENTER),
    01421           NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_CONTOUR), SetDataTip(SPR_IMG_SHOW_COUNTOURS, STR_SMALLMAP_TOOLTIP_SHOW_LAND_CONTOURS_ON_MAP),
    01422           NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_VEHICLES), SetDataTip(SPR_IMG_SHOW_VEHICLES, STR_SMALLMAP_TOOLTIP_SHOW_VEHICLES_ON_MAP),
    01423           NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_INDUSTRIES), SetDataTip(SPR_IMG_INDUSTRY, STR_SMALLMAP_TOOLTIP_SHOW_INDUSTRIES_ON_MAP),
    01424         EndContainer(),
    01425         /* Bottom button row. */
    01426         NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
    01427           NWidget(WWT_PUSHIMGBTN, COLOUR_BROWN, SM_WIDGET_ZOOM_OUT), SetDataTip(SPR_IMG_ZOOMOUT, STR_TOOLBAR_TOOLTIP_ZOOM_THE_VIEW_OUT),
    01428           NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_TOGGLETOWNNAME), SetDataTip(SPR_IMG_TOWN, STR_SMALLMAP_TOOLTIP_TOGGLE_TOWN_NAMES_ON_OFF),
    01429           NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_ROUTES), SetDataTip(SPR_IMG_SHOW_ROUTES, STR_SMALLMAP_TOOLTIP_SHOW_TRANSPORT_ROUTES_ON),
    01430           NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_VEGETATION), SetDataTip(SPR_IMG_PLANTTREES, STR_SMALLMAP_TOOLTIP_SHOW_VEGETATION_ON_MAP),
    01431           NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_OWNERS), SetDataTip(SPR_IMG_COMPANY_GENERAL, STR_SMALLMAP_TOOLTIP_SHOW_LAND_OWNERS_ON_MAP),
    01432         EndContainer(),
    01433         NWidget(NWID_SPACER), SetResize(0, 1),
    01434       EndContainer(),
    01435     EndContainer(),
    01436   EndContainer(),
    01437 };
    01438
    01439 static NWidgetBase *SmallMapDisplay(int *biggest_index)
    01440 {
    01441   NWidgetContainer *map_display = new NWidgetSmallmapDisplay;
    01442
    01443   MakeNWidgets(_nested_smallmap_display, lengthof(_nested_smallmap_display), biggest_index, map_display);
    01444   MakeNWidgets(_nested_smallmap_bar, lengthof(_nested_smallmap_bar), biggest_index, map_display);
    01445   return map_display;
    01446 }
    01447
    01448
    01449 static const NWidgetPart _nested_smallmap_widgets[] = {
    01450   NWidget(NWID_HORIZONTAL),
    01451     NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
    01452     NWidget(WWT_CAPTION, COLOUR_BROWN, SM_WIDGET_CAPTION), SetDataTip(STR_SMALLMAP_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
    01453     NWidget(WWT_SHADEBOX, COLOUR_BROWN),
    01454     NWidget(WWT_STICKYBOX, COLOUR_BROWN),
    01455   EndContainer(),
    01456   NWidgetFunction(SmallMapDisplay), // Smallmap display and legend bar + image buttons.
    01457   /* Bottom button row and resize box. */
    01458   NWidget(NWID_HORIZONTAL),
    01459     NWidget(WWT_PANEL, COLOUR_BROWN),
    01460       NWidget(NWID_HORIZONTAL),
    01461         NWidget(NWID_SELECTION, INVALID_COLOUR, SM_WIDGET_SELECTINDUSTRIES),
    01462           NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
    01463             NWidget(WWT_TEXTBTN, COLOUR_BROWN, SM_WIDGET_ENABLEINDUSTRIES), SetDataTip(STR_SMALLMAP_ENABLE_ALL, STR_SMALLMAP_TOOLTIP_ENABLE_ALL),
    01464             NWidget(WWT_TEXTBTN, COLOUR_BROWN, SM_WIDGET_DISABLEINDUSTRIES), SetDataTip(STR_SMALLMAP_DISABLE_ALL, STR_SMALLMAP_TOOLTIP_DISABLE_ALL),
    01465             NWidget(WWT_TEXTBTN, COLOUR_BROWN, SM_WIDGET_SHOW_HEIGHT), SetDataTip(STR_SMALLMAP_SHOW_HEIGHT, STR_SMALLMAP_TOOLTIP_SHOW_HEIGHT),
    01466           EndContainer(),
    01467           NWidget(NWID_SPACER), SetFill(1, 1),
    01468         EndContainer(),
    01469         NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
    01470       EndContainer(),
    01471     EndContainer(),
    01472     NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
    01473   EndContainer(),
    01474 };
    01475
    01476 static const WindowDesc _smallmap_desc(
    01477   WDP_AUTO, 446, 314,
    01478   WC_SMALLMAP, WC_NONE,
    01479   WDF_UNCLICK_BUTTONS,
    01480   _nested_smallmap_widgets, lengthof(_nested_smallmap_widgets)
    01481 );
    01482
    01483 void ShowSmallMap()
    01484 {
    01485   AllocateWindowDescFront<SmallMapWindow>(&_smallmap_desc, 0);
    01486 }
    01487
    01496 bool ScrollMainWindowTo(int x, int y, int z, bool instant)
    01497 {
    01498   bool res = ScrollWindowTo(x, y, z, FindWindowById(WC_MAIN_WINDOW, 0), instant);
    01499
    01500   /* If a user scrolls to a tile (via what way what so ever) and already is on
    01501    * that tile (e.g.: pressed twice), move the smallmap to that location,
    01502    * so you directly see where you are on the smallmap. */
    01503
    01504   if (res) return res;
    01505
    01506   SmallMapWindow *w = dynamic_cast<SmallMapWindow*>(FindWindowById(WC_SMALLMAP, 0));
    01507   if (w != NULL) w->SmallMapCenterOnCurrentPos();
    01508
    01509   return res;
    01510 }