industry_gui.cpp

Go to the documentation of this file.
00001 /* $Id: industry_gui.cpp 14422 2008-09-30 20:51:04Z rubidium $ */
00002 
00005 #include "stdafx.h"
00006 #include "openttd.h"
00007 #include "debug.h"
00008 #include "gui.h"
00009 #include "window_gui.h"
00010 #include "textbuf_gui.h"
00011 #include "command_func.h"
00012 #include "viewport_func.h"
00013 #include "gfx_func.h"
00014 #include "industry.h"
00015 #include "town.h"
00016 #include "variables.h"
00017 #include "cheat_func.h"
00018 #include "cargotype.h"
00019 #include "newgrf.h"
00020 #include "newgrf_callbacks.h"
00021 #include "newgrf_industries.h"
00022 #include "newgrf_text.h"
00023 #include "strings_func.h"
00024 #include "map_func.h"
00025 #include "company_func.h"
00026 #include "settings_type.h"
00027 #include "tilehighlight_func.h"
00028 #include "string_func.h"
00029 #include "sortlist_type.h"
00030 #include "widgets/dropdown_func.h"
00031 #include "company_base.h"
00032 
00033 #include "table/strings.h"
00034 #include "table/sprites.h"
00035 
00036 bool _ignore_restrictions;
00037 
00038 enum CargoSuffixType {
00039   CST_FUND,
00040   CST_VIEW,
00041   CST_DIR,
00042 };
00043 
00058 static StringID GetCargoSuffix(uint cargo, CargoSuffixType cst, Industry *ind, IndustryType ind_type, const IndustrySpec *indspec)
00059 {
00060   if (HasBit(indspec->callback_flags, CBM_IND_CARGO_SUFFIX)) {
00061     uint16 callback = GetIndustryCallback(CBID_INDUSTRY_CARGO_SUFFIX, 0, (cst << 8) | cargo, ind, ind_type, (cst != CST_FUND) ? ind->xy : INVALID_TILE);
00062     if (GB(callback, 0, 8) != 0xFF) return GetGRFStringID(indspec->grf_prop.grffile->grfid, 0xD000 + callback);
00063   }
00064   return STR_EMPTY;
00065 }
00066 
00068 enum DynamicPlaceIndustriesWidgets {
00069   DPIW_CLOSEBOX = 0,
00070   DPIW_CAPTION,
00071   DPIW_MATRIX_WIDGET,
00072   DPIW_SCROLLBAR,
00073   DPIW_INFOPANEL,
00074   DPIW_FUND_WIDGET,
00075   DPIW_RESIZE_WIDGET,
00076 };
00077 
00079 static const Widget _build_industry_widgets[] = {
00080 {   WWT_CLOSEBOX,    RESIZE_NONE,  COLOUR_DARK_GREEN,     0,    10,     0,    13, STR_00C5,                       STR_018B_CLOSE_WINDOW},            // DPIW_CLOSEBOX
00081 {    WWT_CAPTION,   RESIZE_RIGHT,  COLOUR_DARK_GREEN,    11,   169,     0,    13, STR_0314_FUND_NEW_INDUSTRY,     STR_018C_WINDOW_TITLE_DRAG_THIS},  // DPIW_CAPTION
00082 {     WWT_MATRIX,      RESIZE_RB,  COLOUR_DARK_GREEN,     0,   157,    14,   118, 0x801,                          STR_INDUSTRY_SELECTION_HINT},      // DPIW_MATRIX_WIDGET
00083 {  WWT_SCROLLBAR,     RESIZE_LRB,  COLOUR_DARK_GREEN,   158,   169,    14,   118, 0x0,                            STR_0190_SCROLL_BAR_SCROLLS_LIST}, // DPIW_SCROLLBAR
00084 {      WWT_PANEL,     RESIZE_RTB,  COLOUR_DARK_GREEN,     0,   169,   119,   199, 0x0,                            STR_NULL},                         // DPIW_INFOPANEL
00085 {    WWT_TEXTBTN,     RESIZE_RTB,  COLOUR_DARK_GREEN,     0,   157,   200,   211, STR_FUND_NEW_INDUSTRY,          STR_NULL},                         // DPIW_FUND_WIDGET
00086 {  WWT_RESIZEBOX,    RESIZE_LRTB,  COLOUR_DARK_GREEN,   158,   169,   200,   211, 0x0,                            STR_RESIZE_BUTTON},                // DPIW_RESIZE_WIDGET
00087 {   WIDGETS_END},
00088 };
00089 
00091 static const WindowDesc _build_industry_desc = {
00092   WDP_AUTO, WDP_AUTO, 170, 212, 170, 212,
00093   WC_BUILD_INDUSTRY, WC_NONE,
00094   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_RESIZABLE,
00095   _build_industry_widgets,
00096 };
00097 
00098 class BuildIndustryWindow : public Window {
00099   int selected_index;                         
00100   IndustryType selected_type;                 
00101   uint16 callback_timer;                      
00102   bool timer_enabled;                         
00103   uint16 count;                               
00104   IndustryType index[NUM_INDUSTRYTYPES + 1];  
00105   StringID text[NUM_INDUSTRYTYPES + 1];       
00106   bool enabled[NUM_INDUSTRYTYPES + 1];        
00107 
00108   void SetupArrays()
00109   {
00110     IndustryType ind;
00111     const IndustrySpec *indsp;
00112 
00113     this->count = 0;
00114 
00115     for (uint i = 0; i < lengthof(this->index); i++) {
00116       this->index[i]   = INVALID_INDUSTRYTYPE;
00117       this->text[i]    = STR_NULL;
00118       this->enabled[i] = false;
00119     }
00120 
00121     if (_game_mode == GM_EDITOR) { // give room for the Many Random "button"
00122       this->index[this->count] = INVALID_INDUSTRYTYPE;
00123       this->count++;
00124       this->timer_enabled = false;
00125     }
00126     /* Fill the arrays with industries.
00127      * The tests performed after the enabled allow to load the industries
00128      * In the same way they are inserted by grf (if any)
00129      */
00130     for (ind = 0; ind < NUM_INDUSTRYTYPES; ind++) {
00131       indsp = GetIndustrySpec(ind);
00132       if (indsp->enabled){
00133         /* Rule is that editor mode loads all industries.
00134          * In game mode, all non raw industries are loaded too
00135          * and raw ones are loaded only when setting allows it */
00136         if (_game_mode != GM_EDITOR && indsp->IsRawIndustry() && _settings_game.construction.raw_industry_construction == 0) {
00137           /* Unselect if the industry is no longer in the list */
00138           if (this->selected_type == ind) this->selected_index = -1;
00139           continue;
00140         }
00141         this->index[this->count] = ind;
00142         this->enabled[this->count] = (_game_mode == GM_EDITOR) || CheckIfCallBackAllowsAvailability(ind, IACT_USERCREATION);
00143         /* Keep the selection to the correct line */
00144         if (this->selected_type == ind) this->selected_index = this->count;
00145         this->count++;
00146       }
00147     }
00148 
00149     /* first indutry type is selected if the current selection is invalid.
00150      * I'll be damned if there are none available ;) */
00151     if (this->selected_index == -1) {
00152       this->selected_index = 0;
00153       this->selected_type = this->index[0];
00154     }
00155   }
00156 
00157 public:
00158   BuildIndustryWindow() : Window(&_build_industry_desc)
00159   {
00160     /* Shorten the window to the equivalant of the additionnal purchase
00161      * info coming from the callback.  SO it will only be available to its full
00162      * height when newindistries are loaded */
00163     if (!_loaded_newgrf_features.has_newindustries) {
00164       this->widget[DPIW_INFOPANEL].bottom -= 44;
00165       this->widget[DPIW_FUND_WIDGET].bottom -= 44;
00166       this->widget[DPIW_FUND_WIDGET].top -= 44;
00167       this->widget[DPIW_RESIZE_WIDGET].bottom -= 44;
00168       this->widget[DPIW_RESIZE_WIDGET].top -= 44;
00169       this->resize.height = this->height -= 44;
00170     }
00171 
00172     this->timer_enabled = _loaded_newgrf_features.has_newindustries;
00173 
00174     this->vscroll.cap = 8; // rows in grid, same in scroller
00175     this->resize.step_height = 13;
00176 
00177     this->selected_index = -1;
00178     this->selected_type = INVALID_INDUSTRYTYPE;
00179 
00180     /* Initialize arrays */
00181     this->SetupArrays();
00182 
00183     this->callback_timer = DAY_TICKS;
00184 
00185     this->FindWindowPlacementAndResize(&_build_industry_desc);
00186   }
00187 
00188   virtual void OnPaint()
00189   {
00190     const IndustrySpec *indsp = (this->selected_type == INVALID_INDUSTRYTYPE) ? NULL : GetIndustrySpec(this->selected_type);
00191     int x_str = this->widget[DPIW_INFOPANEL].left + 3;
00192     int y_str = this->widget[DPIW_INFOPANEL].top + 3;
00193     const Widget *wi = &this->widget[DPIW_INFOPANEL];
00194     int max_width = wi->right - wi->left - 4;
00195 
00196     /* Raw industries might be prospected. Show this fact by changing the string
00197      * In Editor, you just build, while ingame, or you fund or you prospect */
00198     if (_game_mode == GM_EDITOR) {
00199       /* We've chosen many random industries but no industries have been specified */
00200       if (indsp == NULL) this->enabled[this->selected_index] = _settings_game.difficulty.number_industries != 0;
00201       this->widget[DPIW_FUND_WIDGET].data = STR_BUILD_NEW_INDUSTRY;
00202     } else {
00203       this->widget[DPIW_FUND_WIDGET].data = (_settings_game.construction.raw_industry_construction == 2 && indsp->IsRawIndustry()) ? STR_PROSPECT_NEW_INDUSTRY : STR_FUND_NEW_INDUSTRY;
00204     }
00205     this->SetWidgetDisabledState(DPIW_FUND_WIDGET, !this->enabled[this->selected_index]);
00206 
00207     SetVScrollCount(this, this->count);
00208 
00209     this->DrawWidgets();
00210 
00211     /* and now with the matrix painting */
00212     for (byte i = 0; i < this->vscroll.cap && ((i + this->vscroll.pos) < this->count); i++) {
00213       int offset = i * 13;
00214       int x = 3;
00215       int y = 16;
00216       bool selected = this->selected_index == i + this->vscroll.pos;
00217 
00218       if (this->index[i + this->vscroll.pos] == INVALID_INDUSTRYTYPE) {
00219         DrawStringTruncated(20, y + offset, STR_MANY_RANDOM_INDUSTRIES, selected ? TC_WHITE : TC_ORANGE, max_width - 25);
00220         continue;
00221       }
00222       const IndustrySpec *indsp = GetIndustrySpec(this->index[i + this->vscroll.pos]);
00223 
00224       /* Draw the name of the industry in white is selected, otherwise, in orange */
00225       DrawStringTruncated(20, y + offset, indsp->name, selected ? TC_WHITE : TC_ORANGE, max_width - 25);
00226       GfxFillRect(x,     y + 1 + offset,  x + 10, y + 7 + offset, selected ? 15 : 0);
00227       GfxFillRect(x + 1, y + 2 + offset,  x +  9, y + 6 + offset, indsp->map_colour);
00228     }
00229 
00230     if (this->selected_type == INVALID_INDUSTRYTYPE) {
00231       DrawStringMultiLine(x_str, y_str, STR_RANDOM_INDUSTRIES_TIP, max_width, wi->bottom - wi->top - 40);
00232       return;
00233     }
00234 
00235     if (_game_mode != GM_EDITOR) {
00236       SetDParam(0, indsp->GetConstructionCost());
00237       DrawStringTruncated(x_str, y_str, STR_482F_COST, TC_FROMSTRING, max_width);
00238       y_str += 11;
00239     }
00240 
00241     /* Draw the accepted cargos, if any. Otherwhise, will print "Nothing" */
00242     StringID str = STR_4827_REQUIRES;
00243     byte p = 0;
00244     SetDParam(0, STR_00D0_NOTHING);
00245     SetDParam(1, STR_EMPTY);
00246     for (byte j = 0; j < lengthof(indsp->accepts_cargo); j++) {
00247       if (indsp->accepts_cargo[j] == CT_INVALID) continue;
00248       if (p > 0) str++;
00249       SetDParam(p++, GetCargo(indsp->accepts_cargo[j])->name);
00250       SetDParam(p++, GetCargoSuffix(j, CST_FUND, NULL, this->selected_type, indsp));
00251     }
00252     DrawStringTruncated(x_str, y_str, str, TC_FROMSTRING, max_width);
00253     y_str += 11;
00254 
00255     /* Draw the produced cargos, if any. Otherwhise, will print "Nothing" */
00256     str = STR_4827_PRODUCES;
00257     p = 0;
00258     SetDParam(0, STR_00D0_NOTHING);
00259     SetDParam(1, STR_EMPTY);
00260     for (byte j = 0; j < lengthof(indsp->produced_cargo); j++) {
00261       if (indsp->produced_cargo[j] == CT_INVALID) continue;
00262       if (p > 0) str++;
00263       SetDParam(p++, GetCargo(indsp->produced_cargo[j])->name);
00264       SetDParam(p++, GetCargoSuffix(j + 3, CST_FUND, NULL, this->selected_type, indsp));
00265     }
00266     DrawStringTruncated(x_str, y_str, str, TC_FROMSTRING, max_width);
00267     y_str += 11;
00268 
00269     /* Get the additional purchase info text, if it has not already been */
00270     if (this->text[this->selected_index] == STR_NULL) {   // Have i been called already?
00271       if (HasBit(indsp->callback_flags, CBM_IND_FUND_MORE_TEXT)) {          // No. Can it be called?
00272         uint16 callback_res = GetIndustryCallback(CBID_INDUSTRY_FUND_MORE_TEXT, 0, 0, NULL, this->selected_type, INVALID_TILE);
00273         if (callback_res != CALLBACK_FAILED) {  // Did it failed?
00274           StringID newtxt = GetGRFStringID(indsp->grf_prop.grffile->grfid, 0xD000 + callback_res);  // No. here's the new string
00275           this->text[this->selected_index] = newtxt;   // Store it for further usage
00276         }
00277       }
00278     }
00279 
00280     /* Draw the Additional purchase text, provided by newgrf callback, if any.
00281      * Otherwhise, will print Nothing */
00282     str = this->text[this->selected_index];
00283     if (str != STR_NULL && str != STR_UNDEFINED) {
00284       SetDParam(0, str);
00285       DrawStringMultiLine(x_str, y_str, STR_JUST_STRING, max_width, wi->bottom - wi->top - 40);
00286     }
00287   }
00288 
00289   virtual void OnDoubleClick(Point pt, int widget)
00290   {
00291     if (widget != DPIW_MATRIX_WIDGET) return;
00292     this->OnClick(pt, DPIW_FUND_WIDGET);
00293   }
00294 
00295   virtual void OnClick(Point pt, int widget)
00296   {
00297     switch (widget) {
00298       case DPIW_MATRIX_WIDGET: {
00299         const IndustrySpec *indsp;
00300         int y = (pt.y - this->widget[DPIW_MATRIX_WIDGET].top) / 13 + this->vscroll.pos ;
00301 
00302         if (y >= 0 && y < count) { // Is it within the boundaries of available data?
00303           this->selected_index = y;
00304           this->selected_type = this->index[y];
00305           indsp = (this->selected_type == INVALID_INDUSTRYTYPE) ? NULL : GetIndustrySpec(this->selected_type);
00306 
00307           this->SetDirty();
00308 
00309           if ((_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && indsp != NULL && indsp->IsRawIndustry()) ||
00310               this->selected_type == INVALID_INDUSTRYTYPE) {
00311             /* Reset the button state if going to prospecting or "build many industries" */
00312             this->RaiseButtons();
00313             ResetObjectToPlace();
00314           }
00315         }
00316       } break;
00317 
00318       case DPIW_FUND_WIDGET: {
00319         if (this->selected_type == INVALID_INDUSTRYTYPE) {
00320           this->HandleButtonClick(DPIW_FUND_WIDGET);
00321 
00322           if (GetNumTowns() == 0) {
00323             ShowErrorMessage(STR_0286_MUST_BUILD_TOWN_FIRST, STR_CAN_T_GENERATE_INDUSTRIES, 0, 0);
00324           } else {
00325             extern void GenerateIndustries();
00326             _generating_world = true;
00327             GenerateIndustries();
00328             _generating_world = false;
00329           }
00330         } else if (_game_mode != GM_EDITOR && _settings_game.construction.raw_industry_construction == 2 && GetIndustrySpec(this->selected_type)->IsRawIndustry()) {
00331           DoCommandP(0, this->selected_type, InteractiveRandom(), NULL, CMD_BUILD_INDUSTRY | CMD_MSG(STR_4830_CAN_T_CONSTRUCT_THIS_INDUSTRY));
00332           this->HandleButtonClick(DPIW_FUND_WIDGET);
00333         } else {
00334           HandlePlacePushButton(this, DPIW_FUND_WIDGET, SPR_CURSOR_INDUSTRY, VHM_RECT, NULL);
00335         }
00336       } break;
00337     }
00338   }
00339 
00340   virtual void OnResize(Point new_size, Point delta)
00341   {
00342     /* Adjust the number of items in the matrix depending of the rezise */
00343     this->vscroll.cap  += delta.y / (int)this->resize.step_height;
00344     this->widget[DPIW_MATRIX_WIDGET].data = (this->vscroll.cap << 8) + 1;
00345   }
00346 
00347   virtual void OnPlaceObject(Point pt, TileIndex tile)
00348   {
00349     bool success = true;
00350     /* We do not need to protect ourselves against "Random Many Industries" in this mode */
00351     const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
00352     uint32 seed = InteractiveRandom();
00353 
00354     if (_game_mode == GM_EDITOR) {
00355       /* Show error if no town exists at all */
00356       if (GetNumTowns() == 0) {
00357         SetDParam(0, indsp->name);
00358         ShowErrorMessage(STR_0286_MUST_BUILD_TOWN_FIRST, STR_0285_CAN_T_BUILD_HERE, pt.x, pt.y);
00359         return;
00360       }
00361 
00362       _current_company = OWNER_NONE;
00363       _generating_world = true;
00364       _ignore_restrictions = true;
00365       success = DoCommandP(tile, (InteractiveRandomRange(indsp->num_table) << 16) | this->selected_type, seed, NULL, CMD_BUILD_INDUSTRY | CMD_MSG(STR_4830_CAN_T_CONSTRUCT_THIS_INDUSTRY));
00366       if (!success) {
00367         SetDParam(0, indsp->name);
00368         ShowErrorMessage(_error_message, STR_0285_CAN_T_BUILD_HERE, pt.x, pt.y);
00369       }
00370 
00371       _ignore_restrictions = false;
00372       _generating_world = false;
00373     } else {
00374       success = DoCommandP(tile, (InteractiveRandomRange(indsp->num_table) << 16) | this->selected_type, seed, NULL, CMD_BUILD_INDUSTRY | CMD_MSG(STR_4830_CAN_T_CONSTRUCT_THIS_INDUSTRY));
00375     }
00376 
00377     /* If an industry has been built, just reset the cursor and the system */
00378     if (success) ResetObjectToPlace();
00379   }
00380 
00381   virtual void OnTick()
00382   {
00383     if (_pause_game != 0) return;
00384     if (!this->timer_enabled) return;
00385     if (--this->callback_timer == 0) {
00386       /* We have just passed another day.
00387        * See if we need to update availability of currently selected industry */
00388       this->callback_timer = DAY_TICKS;  //restart counter
00389 
00390       const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
00391 
00392       if (indsp->enabled) {
00393         bool call_back_result = CheckIfCallBackAllowsAvailability(this->selected_type, IACT_USERCREATION);
00394 
00395         /* Only if result does match the previous state would it require a redraw. */
00396         if (call_back_result != this->enabled[this->selected_index]) {
00397           this->enabled[this->selected_index] = call_back_result;
00398           this->SetDirty();
00399         }
00400       }
00401     }
00402   }
00403 
00404   virtual void OnTimeout()
00405   {
00406     this->RaiseButtons();
00407   }
00408 
00409   virtual void OnPlaceObjectAbort()
00410   {
00411     this->RaiseButtons();
00412   }
00413 
00414   virtual void OnInvalidateData(int data = 0)
00415   {
00416     this->SetupArrays();
00417     this->SetDirty();
00418   }
00419 };
00420 
00421 void ShowBuildIndustryWindow()
00422 {
00423   if (_game_mode != GM_EDITOR && !IsValidCompanyID(_current_company)) return;
00424   if (BringWindowToFrontById(WC_BUILD_INDUSTRY, 0)) return;
00425   new BuildIndustryWindow();
00426 }
00427 
00428 static void UpdateIndustryProduction(Industry *i);
00429 
00430 static inline bool IsProductionMinimum(const Industry *i, int pt)
00431 {
00432   return i->production_rate[pt] == 0;
00433 }
00434 
00435 static inline bool IsProductionMaximum(const Industry *i, int pt)
00436 {
00437   return i->production_rate[pt] >= 255;
00438 }
00439 
00440 static inline bool IsProductionAlterable(const Industry *i)
00441 {
00442   return ((_game_mode == GM_EDITOR || _cheats.setup_prod.value) &&
00443       (i->accepts_cargo[0] == CT_INVALID || i->accepts_cargo[0] == CT_VALUABLES));
00444 }
00445 
00447 enum IndustryViewWidgets {
00448   IVW_CLOSEBOX = 0,
00449   IVW_CAPTION,
00450   IVW_STICKY,
00451   IVW_BACKGROUND,
00452   IVW_VIEWPORT,
00453   IVW_INFO,
00454   IVW_GOTO,
00455   IVW_SPACER,
00456   IVW_RESIZE,
00457 };
00458 
00459 class IndustryViewWindow : public Window
00460 {
00461   byte editbox_line;        
00462   byte clicked_line;        
00463   byte clicked_button;      
00464   byte production_offset_y; 
00465 
00466 public:
00467   IndustryViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window(desc, window_number)
00468   {
00469     this->flags4 |= WF_DISABLE_VP_SCROLL;
00470     this->editbox_line = 0;
00471     this->clicked_line = 0;
00472     this->clicked_button = 0;
00473     InitializeWindowViewport(this, 3, 17, 254, 86, GetIndustry(window_number)->xy + TileDiffXY(1, 1), ZOOM_LVL_INDUSTRY);
00474     this->FindWindowPlacementAndResize(desc);
00475   }
00476 
00477   virtual void OnPaint()
00478   {
00479     Industry *i = GetIndustry(this->window_number);
00480     const IndustrySpec *ind = GetIndustrySpec(i->type);
00481     int y = this->widget[IVW_INFO].top + 1;
00482     bool first = true;
00483     bool has_accept = false;
00484 
00485     SetDParam(0, this->window_number);
00486     this->DrawWidgets();
00487 
00488     if (HasBit(ind->callback_flags, CBM_IND_PRODUCTION_CARGO_ARRIVAL) || HasBit(ind->callback_flags, CBM_IND_PRODUCTION_256_TICKS)) {
00489       for (byte j = 0; j < lengthof(i->accepts_cargo); j++) {
00490         if (i->accepts_cargo[j] == CT_INVALID) continue;
00491         has_accept = true;
00492         if (first) {
00493           DrawStringTruncated(2, y, STR_INDUSTRY_WINDOW_WAITING_FOR_PROCESSING, TC_FROMSTRING, this->widget[IVW_INFO].right - 2);
00494           y += 10;
00495           first = false;
00496         }
00497         SetDParam(0, i->accepts_cargo[j]);
00498         SetDParam(1, i->incoming_cargo_waiting[j]);
00499         SetDParam(2, GetCargoSuffix(j, CST_VIEW, i, i->type, ind));
00500         DrawStringTruncated(4, y, STR_INDUSTRY_WINDOW_WAITING_STOCKPILE_CARGO, TC_FROMSTRING, this->widget[IVW_INFO].right - 4);
00501         y += 10;
00502       }
00503     } else {
00504       StringID str = STR_4827_REQUIRES;
00505       byte p = 0;
00506       for (byte j = 0; j < lengthof(i->accepts_cargo); j++) {
00507         if (i->accepts_cargo[j] == CT_INVALID) continue;
00508         has_accept = true;
00509         if (p > 0) str++;
00510         SetDParam(p++, GetCargo(i->accepts_cargo[j])->name);
00511         SetDParam(p++, GetCargoSuffix(j, CST_VIEW, i, i->type, ind));
00512       }
00513       if (has_accept) {
00514         DrawStringTruncated(2, y, str, TC_FROMSTRING, this->widget[IVW_INFO].right - 2);
00515         y += 10;
00516       }
00517     }
00518 
00519     first = true;
00520     for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
00521       if (i->produced_cargo[j] == CT_INVALID) continue;
00522       if (first) {
00523         if (has_accept) y += 10;
00524         DrawStringTruncated(2, y, STR_482A_PRODUCTION_LAST_MONTH, TC_FROMSTRING, this->widget[IVW_INFO].right - 2);
00525         y += 10;
00526         this->production_offset_y = y;
00527         first = false;
00528       }
00529 
00530       SetDParam(0, i->produced_cargo[j]);
00531       SetDParam(1, i->last_month_production[j]);
00532       SetDParam(2, GetCargoSuffix(j + 3, CST_VIEW, i, i->type, ind));
00533 
00534       SetDParam(3, i->last_month_pct_transported[j] * 100 >> 8);
00535       uint x = 4 + (IsProductionAlterable(i) ? 30 : 0);
00536       DrawStringTruncated(x, y, STR_482B_TRANSPORTED, TC_FROMSTRING, this->widget[IVW_INFO].right - x);
00537       /* Let's put out those buttons.. */
00538       if (IsProductionAlterable(i)) {
00539         DrawArrowButtons(5, y, COLOUR_YELLOW, (this->clicked_line == j + 1) ? this->clicked_button : 0,
00540             !IsProductionMinimum(i, j), !IsProductionMaximum(i, j));
00541       }
00542       y += 10;
00543     }
00544 
00545     /* Get the extra message for the GUI */
00546     if (HasBit(ind->callback_flags, CBM_IND_WINDOW_MORE_TEXT)) {
00547       uint16 callback_res = GetIndustryCallback(CBID_INDUSTRY_WINDOW_MORE_TEXT, 0, 0, i, i->type, i->xy);
00548       if (callback_res != CALLBACK_FAILED) {
00549         StringID message = GetGRFStringID(ind->grf_prop.grffile->grfid, 0xD000 + callback_res);
00550         if (message != STR_NULL && message != STR_UNDEFINED) {
00551           const Widget *wi = &this->widget[IVW_INFO];
00552           y += 10;
00553 
00554           PrepareTextRefStackUsage(6);
00555           /* Use all the available space left from where we stand up to the end of the window */
00556           y += DrawStringMultiLine(2, y, message, wi->right - wi->left - 4, -1);
00557           StopTextRefStackUsage();
00558         }
00559       }
00560     }
00561 
00562     if (y > this->widget[IVW_INFO].bottom) {
00563       this->SetDirty();
00564       ResizeWindowForWidget(this, IVW_INFO, 0, y - this->widget[IVW_INFO].top);
00565       this->SetDirty();
00566       return;
00567     }
00568 
00569     this->DrawViewport();
00570   }
00571 
00572   virtual void OnClick(Point pt, int widget)
00573   {
00574     Industry *i;
00575 
00576     switch (widget) {
00577       case IVW_INFO: {
00578         int line, x;
00579 
00580         i = GetIndustry(this->window_number);
00581 
00582         /* We should work if needed.. */
00583         if (!IsProductionAlterable(i)) return;
00584         x = pt.x;
00585         line = (pt.y - this->production_offset_y) / 10;
00586         if (pt.y >= this->production_offset_y && IsInsideMM(line, 0, 2) && i->produced_cargo[line] != CT_INVALID) {
00587           if (IsInsideMM(x, 5, 25) ) {
00588             /* Clicked buttons, decrease or increase production */
00589             if (x < 15) {
00590               if (IsProductionMinimum(i, line)) return;
00591               i->production_rate[line] = max(i->production_rate[line] / 2, 0);
00592             } else {
00593               /* a zero production industry is unlikely to give anything but zero, so push it a little bit */
00594               int new_prod = i->production_rate[line] == 0 ? 1 : i->production_rate[line] * 2;
00595               if (IsProductionMaximum(i, line)) return;
00596               i->production_rate[line] = minu(new_prod, 255);
00597             }
00598 
00599             UpdateIndustryProduction(i);
00600             this->SetDirty();
00601             this->flags4 |= WF_TIMEOUT_BEGIN;
00602             this->clicked_line = line + 1;
00603             this->clicked_button = (x < 15 ? 1 : 2);
00604           } else if (IsInsideMM(x, 34, 160)) {
00605             /* clicked the text */
00606             this->editbox_line = line;
00607             SetDParam(0, i->production_rate[line] * 8);
00608             ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_CONFIG_GAME_PRODUCTION, 10, 100, this, CS_ALPHANUMERAL, QSF_NONE);
00609           }
00610         }
00611       } break;
00612 
00613       case IVW_GOTO:
00614         i = GetIndustry(this->window_number);
00615         if (_ctrl_pressed) {
00616           ShowExtraViewPortWindow(i->xy + TileDiffXY(1, 1));
00617         } else {
00618           ScrollMainWindowToTile(i->xy + TileDiffXY(1, 1));
00619         }
00620         break;
00621     }
00622   }
00623 
00624   virtual void OnTimeout()
00625   {
00626     this->clicked_line = 0;
00627     this->clicked_button = 0;
00628     this->SetDirty();
00629   }
00630 
00631   virtual void OnResize(Point new_size, Point delta)
00632   {
00633     this->viewport->width            += delta.x;
00634     this->viewport->height           += delta.y;
00635     this->viewport->virtual_width    += delta.x;
00636     this->viewport->virtual_height   += delta.y;
00637     this->viewport->dest_scrollpos_x -= delta.x;
00638     this->viewport->dest_scrollpos_y -= delta.y;
00639     UpdateViewportPosition(this);
00640   }
00641 
00642   virtual void OnQueryTextFinished(char *str)
00643   {
00644     if (StrEmpty(str)) return;
00645 
00646     Industry* i = GetIndustry(this->window_number);
00647     int line = this->editbox_line;
00648 
00649     i->production_rate[line] = ClampU(atoi(str), 0, 255);
00650     UpdateIndustryProduction(i);
00651     this->SetDirty();
00652   }
00653 };
00654 
00655 static void UpdateIndustryProduction(Industry *i)
00656 {
00657   for (byte j = 0; j < lengthof(i->produced_cargo); j++) {
00658     if (i->produced_cargo[j] != CT_INVALID) {
00659       i->last_month_production[j] = 8 * i->production_rate[j];
00660     }
00661   }
00662 }
00663 
00665 static const Widget _industry_view_widgets[] = {
00666 {   WWT_CLOSEBOX,   RESIZE_NONE,  COLOUR_CREAM,     0,    10,     0,    13, STR_00C5,          STR_018B_CLOSE_WINDOW},            // IVW_CLOSEBOX
00667 {    WWT_CAPTION,  RESIZE_RIGHT,  COLOUR_CREAM,    11,   247,     0,    13, STR_4801,          STR_018C_WINDOW_TITLE_DRAG_THIS},  // IVW_CAPTION
00668 {  WWT_STICKYBOX,     RESIZE_LR,  COLOUR_CREAM,   248,   259,     0,    13, 0x0,               STR_STICKY_BUTTON},                // IVW_STICKY
00669 {      WWT_PANEL,     RESIZE_RB,  COLOUR_CREAM,     0,   259,    14,   105, 0x0,               STR_NULL},                         // IVW_BACKGROUND
00670 {      WWT_INSET,     RESIZE_RB,  COLOUR_CREAM,     2,   257,    16,   103, 0x0,               STR_NULL},                         // IVW_VIEWPORT
00671 {      WWT_PANEL,    RESIZE_RTB,  COLOUR_CREAM,     0,   259,   106,   107, 0x0,               STR_NULL},                         // IVW_INFO
00672 { WWT_PUSHTXTBTN,     RESIZE_TB,  COLOUR_CREAM,     0,   129,   108,   119, STR_00E4_LOCATION, STR_482C_CENTER_THE_MAIN_VIEW_ON}, // IVW_GOTO
00673 {      WWT_PANEL,    RESIZE_RTB,  COLOUR_CREAM,   130,   247,   108,   119, 0x0,               STR_NULL},                         // IVW_SPACER
00674 {  WWT_RESIZEBOX,   RESIZE_LRTB,  COLOUR_CREAM,   248,   259,   108,   119, 0x0,               STR_RESIZE_BUTTON},                // IVW_RESIZE
00675 {   WIDGETS_END},
00676 };
00677 
00679 static const WindowDesc _industry_view_desc = {
00680   WDP_AUTO, WDP_AUTO, 260, 120, 260, 120,
00681   WC_INDUSTRY_VIEW, WC_NONE,
00682   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
00683   _industry_view_widgets,
00684 };
00685 
00686 void ShowIndustryViewWindow(int industry)
00687 {
00688   AllocateWindowDescFront<IndustryViewWindow>(&_industry_view_desc, industry);
00689 }
00690 
00692 enum IndustryDirectoryWidgets {
00693   IDW_CLOSEBOX = 0,
00694   IDW_CAPTION,
00695   IDW_STICKY,
00696   IDW_DROPDOWN_ORDER,
00697   IDW_DROPDOWN_CRITERIA,
00698   IDW_SPACER,
00699   IDW_INDUSTRY_LIST,
00700   IDW_SCROLLBAR,
00701   IDW_RESIZE,
00702 };
00703 
00705 static const Widget _industry_directory_widgets[] = {
00706 {   WWT_CLOSEBOX,   RESIZE_NONE,  COLOUR_BROWN,     0,    10,     0,    13, STR_00C5,                STR_018B_CLOSE_WINDOW},             // IDW_CLOSEBOX
00707 {    WWT_CAPTION,  RESIZE_RIGHT,  COLOUR_BROWN,    11,   415,     0,    13, STR_INDUSTRYDIR_CAPTION, STR_018C_WINDOW_TITLE_DRAG_THIS},   // IDW_CAPTION
00708 {  WWT_STICKYBOX,     RESIZE_LR,  COLOUR_BROWN,   416,   427,     0,    13, 0x0,                     STR_STICKY_BUTTON},                 // IDW_STICKY
00709 
00710 {    WWT_TEXTBTN,   RESIZE_NONE,  COLOUR_BROWN,     0,    80,    14,    25, STR_SORT_BY,             STR_SORT_ORDER_TIP},                // IDW_DROPDOWN_ORDER
00711 {   WWT_DROPDOWN,   RESIZE_NONE,  COLOUR_BROWN,    81,   243,    14,    25, 0x0,                     STR_SORT_CRITERIA_TIP},             // IDW_DROPDOWN_CRITERIA
00712 {      WWT_PANEL,  RESIZE_RIGHT,  COLOUR_BROWN,   244,   415,    14,    25, 0x0,                     STR_NULL},                          // IDW_SPACER
00713 
00714 {      WWT_PANEL,     RESIZE_RB,  COLOUR_BROWN,     0,   415,    26,   189, 0x0,                     STR_INDUSTRYDIR_LIST_CAPTION},      // IDW_INDUSRTY_LIST
00715 {  WWT_SCROLLBAR,    RESIZE_LRB,  COLOUR_BROWN,   416,   427,    14,   177, 0x0,                     STR_0190_SCROLL_BAR_SCROLLS_LIST},  // IDW_SCROLLBAR
00716 {  WWT_RESIZEBOX,   RESIZE_LRTB,  COLOUR_BROWN,   416,   427,   178,   189, 0x0,                     STR_RESIZE_BUTTON},                 // IDW_RESIZE
00717 {   WIDGETS_END},
00718 };
00719 
00720 typedef GUIList<const Industry*> GUIIndustryList;
00721 
00722 
00726 class IndustryDirectoryWindow : public Window {
00727 protected:
00728   /* Runtime saved values */
00729   static Listing last_sorting;
00730   static const Industry *last_industry;
00731 
00732   /* Constants for sorting stations */
00733   static const StringID sorter_names[];
00734   static GUIIndustryList::SortFunction *const sorter_funcs[];
00735 
00736   GUIIndustryList industries;
00737 
00739   void BuildIndustriesList()
00740   {
00741     if (!this->industries.NeedRebuild()) return;
00742 
00743     this->industries.Clear();
00744 
00745     DEBUG(misc, 3, "Building industry list");
00746 
00747     const Industry *i;
00748     FOR_ALL_INDUSTRIES(i) {
00749       *this->industries.Append() = i;
00750     }
00751 
00752     this->industries.Compact();
00753     this->industries.RebuildDone();
00754   }
00755 
00763   static inline int GetCargoTransportedPercentsIfValid(const Industry *i, uint id)
00764   {
00765     assert(id < lengthof(i->produced_cargo));
00766 
00767     if (i->produced_cargo[id] == CT_INVALID) return 101;
00768     return i->last_month_pct_transported[id] * 100 >> 8;
00769   }
00770 
00778   static int GetCargoTransportedSortValue(const Industry *i)
00779   {
00780     int p1 = GetCargoTransportedPercentsIfValid(i, 0);
00781     int p2 = GetCargoTransportedPercentsIfValid(i, 1);
00782 
00783     if (p1 > p2) Swap(p1, p2); // lower value has higher priority
00784 
00785     return (p1 << 8) + p2;
00786   }
00787 
00789   static int CDECL IndustryNameSorter(const Industry* const *a, const Industry* const *b)
00790   {
00791     static char buf_cache[96];
00792     static char buf[96];
00793 
00794     SetDParam(0, (*a)->town->index);
00795     GetString(buf, STR_TOWN, lastof(buf));
00796 
00797     if (*b != last_industry) {
00798       last_industry = *b;
00799       SetDParam(0, (*b)->town->index);
00800       GetString(buf_cache, STR_TOWN, lastof(buf_cache));
00801     }
00802 
00803     return strcmp(buf, buf_cache);
00804   }
00805 
00807   static int CDECL IndustryTypeSorter(const Industry* const *a, const Industry* const *b)
00808   {
00809     int r = (*a)->type - (*b)->type;
00810     return (r == 0) ? IndustryNameSorter(a, b) : r;
00811   }
00812 
00814   static int CDECL IndustryProductionSorter(const Industry* const *a, const Industry* const *b)
00815   {
00816     int r = 0;
00817 
00818     if ((*a)->produced_cargo[0] == CT_INVALID) {
00819       if ((*b)->produced_cargo[0] != CT_INVALID) return -1;
00820     } else {
00821       if ((*b)->produced_cargo[0] == CT_INVALID) return 1;
00822 
00823       r = ((*a)->last_month_production[0] + (*a)->last_month_production[1]) -
00824           ((*b)->last_month_production[0] + (*b)->last_month_production[1]);
00825     }
00826 
00827     return (r == 0) ? IndustryNameSorter(a, b) : r;
00828   }
00829 
00831   static int CDECL IndustryTransportedCargoSorter(const Industry* const *a, const Industry* const *b)
00832   {
00833     int r = GetCargoTransportedSortValue(*a) -