graph_gui.cpp

Go to the documentation of this file.
00001 /* $Id: graph_gui.cpp 20576 2010-08-20 10:31:20Z rubidium $ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
00008  */
00009 
00012 #include "stdafx.h"
00013 #include "openttd.h"
00014 #include "graph_gui.h"
00015 #include "window_gui.h"
00016 #include "company_base.h"
00017 #include "company_gui.h"
00018 #include "economy_func.h"
00019 #include "cargotype.h"
00020 #include "strings_func.h"
00021 #include "window_func.h"
00022 #include "date_func.h"
00023 #include "gfx_func.h"
00024 #include "sortlist_type.h"
00025 #include "core/geometry_func.hpp"
00026 
00027 #include "table/strings.h"
00028 #include "table/sprites.h"
00029 
00030 /* Bitmasks of company and cargo indices that shouldn't be drawn. */
00031 static uint _legend_excluded_companies;
00032 static uint _legend_excluded_cargo;
00033 
00034 /* Apparently these don't play well with enums. */
00035 static const OverflowSafeInt64 INVALID_DATAPOINT(INT64_MAX); // Value used for a datapoint that shouldn't be drawn.
00036 static const uint INVALID_DATAPOINT_POS = UINT_MAX;  // Used to determine if the previous point was drawn.
00037 
00038 /****************/
00039 /* GRAPH LEGEND */
00040 /****************/
00041 
00043 enum GraphLegendWidgetNumbers {
00044   GLW_BACKGROUND,
00045 
00046   GLW_FIRST_COMPANY,
00047   GLW_LAST_COMPANY = GLW_FIRST_COMPANY + MAX_COMPANIES - 1,
00048 };
00049 
00050 struct GraphLegendWindow : Window {
00051   GraphLegendWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
00052   {
00053     this->InitNested(desc, window_number);
00054 
00055     for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) {
00056       if (!HasBit(_legend_excluded_companies, c)) this->LowerWidget(c + GLW_FIRST_COMPANY);
00057 
00058       this->OnInvalidateData(c);
00059     }
00060   }
00061 
00062   virtual void OnPaint()
00063   {
00064     this->DrawWidgets();
00065   }
00066 
00067   virtual void DrawWidget(const Rect &r, int widget) const
00068   {
00069     if (!IsInsideMM(widget, GLW_FIRST_COMPANY, MAX_COMPANIES + GLW_FIRST_COMPANY)) return;
00070 
00071     CompanyID cid = (CompanyID)(widget - GLW_FIRST_COMPANY);
00072 
00073     if (!Company::IsValidID(cid)) return;
00074 
00075     bool rtl = _dynlang.text_dir == TD_RTL;
00076 
00077     DrawCompanyIcon(cid, rtl ? r.right - 16 : r.left + 2, r.top + 2 + (FONT_HEIGHT_NORMAL - 10) / 2);
00078 
00079     SetDParam(0, cid);
00080     SetDParam(1, cid);
00081     DrawString(r.left + (rtl ? WD_FRAMERECT_LEFT : 19), r.right - (rtl ? 19 : WD_FRAMERECT_RIGHT), r.top + WD_FRAMERECT_TOP, STR_COMPANY_NAME_COMPANY_NUM, HasBit(_legend_excluded_companies, cid) ? TC_BLACK : TC_WHITE);
00082   }
00083 
00084   virtual void OnClick(Point pt, int widget, int click_count)
00085   {
00086     if (!IsInsideMM(widget, GLW_FIRST_COMPANY, MAX_COMPANIES + GLW_FIRST_COMPANY)) return;
00087 
00088     ToggleBit(_legend_excluded_companies, widget - GLW_FIRST_COMPANY);
00089     this->ToggleWidgetLoweredState(widget);
00090     this->SetDirty();
00091     InvalidateWindowData(WC_INCOME_GRAPH, 0);
00092     InvalidateWindowData(WC_OPERATING_PROFIT, 0);
00093     InvalidateWindowData(WC_DELIVERED_CARGO, 0);
00094     InvalidateWindowData(WC_PERFORMANCE_HISTORY, 0);
00095     InvalidateWindowData(WC_COMPANY_VALUE, 0);
00096   }
00097 
00098   virtual void OnInvalidateData(int data)
00099   {
00100     if (Company::IsValidID(data)) return;
00101 
00102     SetBit(_legend_excluded_companies, data);
00103     this->RaiseWidget(data + GLW_FIRST_COMPANY);
00104   }
00105 };
00106 
00113 static NWidgetBase *MakeNWidgetCompanyLines(int *biggest_index)
00114 {
00115   NWidgetVertical *vert = new NWidgetVertical();
00116 
00117   for (int widnum = GLW_FIRST_COMPANY; widnum <= GLW_LAST_COMPANY; widnum++) {
00118     NWidgetBackground *panel = new NWidgetBackground(WWT_PANEL, COLOUR_GREY, widnum);
00119     panel->SetMinimalSize(246, FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM);
00120     panel->SetFill(1, 0);
00121     panel->SetDataTip(0x0, STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP);
00122     vert->Add(panel);
00123   }
00124   *biggest_index = GLW_LAST_COMPANY;
00125   return vert;
00126 }
00127 
00128 static const NWidgetPart _nested_graph_legend_widgets[] = {
00129   NWidget(NWID_HORIZONTAL),
00130     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00131     NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_GRAPH_KEY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00132     NWidget(WWT_SHADEBOX, COLOUR_GREY),
00133     NWidget(WWT_STICKYBOX, COLOUR_GREY),
00134   EndContainer(),
00135   NWidget(WWT_PANEL, COLOUR_GREY, GLW_BACKGROUND),
00136     NWidget(NWID_SPACER), SetMinimalSize(0, 2),
00137     NWidget(NWID_HORIZONTAL),
00138       NWidget(NWID_SPACER), SetMinimalSize(2, 0),
00139       NWidgetFunction(MakeNWidgetCompanyLines),
00140       NWidget(NWID_SPACER), SetMinimalSize(2, 0),
00141     EndContainer(),
00142   EndContainer(),
00143 };
00144 
00145 static const WindowDesc _graph_legend_desc(
00146   WDP_AUTO, 0, 0,
00147   WC_GRAPH_LEGEND, WC_NONE,
00148   0,
00149   _nested_graph_legend_widgets, lengthof(_nested_graph_legend_widgets)
00150 );
00151 
00152 static void ShowGraphLegend()
00153 {
00154   AllocateWindowDescFront<GraphLegendWindow>(&_graph_legend_desc, 0);
00155 }
00156 
00157 /******************/
00158 /* BASE OF GRAPHS */
00159 /*****************/
00160 
00162 enum CompanyValueWidgets {
00163   BGW_KEY_BUTTON,
00164   BGW_BACKGROUND,
00165 };
00166 
00167 struct BaseGraphWindow : Window {
00168 protected:
00169   enum {
00170     GRAPH_MAX_DATASETS = 32,
00171     GRAPH_AXIS_LINE_COLOUR  = 215,
00172     GRAPH_NUM_MONTHS = 24, 
00173 
00174     GRAPH_NUM_LINES_Y = 9, 
00175     /* 9 is convenient as that means the distance between them is the gd_height of the graph / 8,
00176      * which is the same
00177      * as height >> 3. */
00178   };
00179 
00180   uint excluded_data; 
00181   byte num_dataset;
00182   byte num_on_x_axis;
00183   bool has_negative_values;
00184   byte num_vert_lines;
00185   static const TextColour graph_axis_label_colour = TC_BLACK; 
00186 
00187   /* The starting month and year that values are plotted against. If month is
00188    * 0xFF, use x_values_start and x_values_increment below instead. */
00189   byte month;
00190   Year year;
00191 
00192   /* These values are used if the graph is being plotted against values
00193    * rather than the dates specified by month and year. */
00194   uint16 x_values_start;
00195   uint16 x_values_increment;
00196 
00197   int graph_widget;
00198   StringID format_str_y_axis;
00199   byte colours[GRAPH_MAX_DATASETS];
00200   OverflowSafeInt64 cost[GRAPH_MAX_DATASETS][GRAPH_NUM_MONTHS]; 
00201 
00207   int64 GetHighestValue() const
00208   {
00209     OverflowSafeInt64 highest_value = 0;
00210 
00211     for (int i = 0; i < this->num_dataset; i++) {
00212       if (HasBit(this->excluded_data, i)) continue;
00213       for (int j = 0; j < this->num_on_x_axis; j++) {
00214         OverflowSafeInt64 datapoint = this->cost[i][j];
00215 
00216         if (datapoint != INVALID_DATAPOINT) {
00217           /* For now, if the graph has negative values the scaling is
00218            * symmetrical about the x axis, so take the absolute value
00219            * of each data point. */
00220           highest_value = max(highest_value, abs(datapoint));
00221         }
00222       }
00223     }
00224 
00225     /* Prevent showing the highest value too close to the graph upper limit. */
00226     highest_value = (11 * highest_value) / 10;
00227     /* Avoid using zero as the highest value. */
00228     if (highest_value == 0) highest_value = GRAPH_NUM_LINES_Y - 1;
00229     /* Round up highest_value so that it will divide cleanly into the number of
00230      * axis labels used. */
00231     int round_val = highest_value % (GRAPH_NUM_LINES_Y - 1);
00232     if (round_val != 0) highest_value += (GRAPH_NUM_LINES_Y - 1 - round_val);
00233 
00234     return highest_value;
00235   }
00236 
00237   uint GetYLabelWidth(int64 highest_value) const
00238   {
00239     /* draw text strings on the y axis */
00240     int64 y_label = highest_value;
00241     int64 y_label_separation = highest_value / (GRAPH_NUM_LINES_Y - 1);
00242 
00243     /* If there are negative values, the graph goes from highest_value to
00244      * -highest_value, not highest_value to 0. */
00245     if (this->has_negative_values) y_label_separation *= 2;
00246 
00247     uint max_width = 0;
00248 
00249     for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
00250       SetDParam(0, this->format_str_y_axis);
00251       SetDParam(1, y_label);
00252       Dimension d = GetStringBoundingBox(STR_GRAPH_Y_LABEL);
00253       if (d.width > max_width) max_width = d.width;
00254 
00255       y_label -= y_label_separation;
00256     }
00257 
00258     return max_width;
00259   }
00260 
00265   void DrawGraph(Rect r) const
00266   {
00267     uint x, y;                       
00268     OverflowSafeInt64 highest_value; 
00269     int x_axis_offset;               
00270 
00271     /* the colours and cost array of GraphDrawer must accomodate
00272      * both values for cargo and companies. So if any are higher, quit */
00273     assert_compile(GRAPH_MAX_DATASETS >= (int)NUM_CARGO && GRAPH_MAX_DATASETS >= (int)MAX_COMPANIES);
00274     assert(this->num_vert_lines > 0);
00275 
00276     byte grid_colour = _colour_gradient[COLOUR_GREY][4];
00277 
00278     /* Rect r will be adjusted to contain just the graph, with labels being
00279      * placed outside the area. */
00280     r.top    += 5 + GetCharacterHeight(FS_SMALL) / 2;
00281     r.bottom -= (this->month == 0xFF ? 1 : 3) * GetCharacterHeight(FS_SMALL) + 4;
00282     r.left   += 9;
00283     r.right  -= 5;
00284 
00285     highest_value = GetHighestValue();
00286 
00287     /* Get width for Y labels */
00288     int label_width = GetYLabelWidth(highest_value);
00289 
00290     r.left += label_width;
00291 
00292     int x_sep = (r.right - r.left) / this->num_vert_lines;
00293     int y_sep = (r.bottom - r.top) / (GRAPH_NUM_LINES_Y - 1);
00294 
00295     /* Redetermine right and bottom edge of graph to fit with the integer
00296      * separation values. */
00297     r.right = r.left + x_sep * this->num_vert_lines;
00298     r.bottom = r.top + y_sep * (GRAPH_NUM_LINES_Y - 1);
00299 
00300     /* Where to draw the X axis */
00301     x_axis_offset = r.bottom - r.top;
00302     if (this->has_negative_values) x_axis_offset /= 2;
00303 
00304     /* Draw the vertical grid lines. */
00305 
00306     /* Don't draw the first line, as that's where the axis will be. */
00307     x = r.left + x_sep;
00308 
00309     for (int i = 0; i < this->num_vert_lines; i++) {
00310       GfxFillRect(x, r.top, x, r.bottom, grid_colour);
00311       x += x_sep;
00312     }
00313 
00314     /* Draw the horizontal grid lines. */
00315     y = r.bottom;
00316 
00317     for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
00318       GfxFillRect(r.left - 3, y, r.left - 1, y, GRAPH_AXIS_LINE_COLOUR);
00319       GfxFillRect(r.left, y, r.right, y, grid_colour);
00320       y -= y_sep;
00321     }
00322 
00323     /* Draw the y axis. */
00324     GfxFillRect(r.left, r.top, r.left, r.bottom, GRAPH_AXIS_LINE_COLOUR);
00325 
00326     /* Draw the x axis. */
00327     y = x_axis_offset + r.top;
00328     GfxFillRect(r.left, y, r.right, y, GRAPH_AXIS_LINE_COLOUR);
00329 
00330     /* Find the largest value that will be drawn. */
00331     if (this->num_on_x_axis == 0)
00332       return;
00333 
00334     assert(this->num_on_x_axis > 0);
00335     assert(this->num_dataset > 0);
00336 
00337     /* draw text strings on the y axis */
00338     int64 y_label = highest_value;
00339     int64 y_label_separation = highest_value / (GRAPH_NUM_LINES_Y - 1);
00340 
00341     /* If there are negative values, the graph goes from highest_value to
00342      * -highest_value, not highest_value to 0. */
00343     if (this->has_negative_values) y_label_separation *= 2;
00344 
00345     y = r.top - GetCharacterHeight(FS_SMALL) / 2;
00346 
00347     for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
00348       SetDParam(0, this->format_str_y_axis);
00349       SetDParam(1, y_label);
00350       DrawString(r.left - label_width - 4, r.left - 4, y, STR_GRAPH_Y_LABEL, graph_axis_label_colour, SA_RIGHT);
00351 
00352       y_label -= y_label_separation;
00353       y += y_sep;
00354     }
00355 
00356     /* draw strings on the x axis */
00357     if (this->month != 0xFF) {
00358       x = r.left;
00359       y = r.bottom + 2;
00360       byte month = this->month;
00361       Year year  = this->year;
00362       for (int i = 0; i < this->num_on_x_axis; i++) {
00363         SetDParam(0, month + STR_MONTH_ABBREV_JAN);
00364         SetDParam(1, month + STR_MONTH_ABBREV_JAN + 2);
00365         SetDParam(2, year);
00366         DrawStringMultiLine(x, x + x_sep, y, this->height, month == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, graph_axis_label_colour);
00367 
00368         month += 3;
00369         if (month >= 12) {
00370           month = 0;
00371           year++;
00372         }
00373         x += x_sep;
00374       }
00375     } else {
00376       /* Draw the label under the data point rather than on the grid line. */
00377       x = r.left;
00378       y = r.bottom + 2;
00379       uint16 label = this->x_values_start;
00380 
00381       for (int i = 0; i < this->num_on_x_axis; i++) {
00382         SetDParam(0, label);
00383         DrawString(x + 1, x + x_sep - 1, y, STR_GRAPH_Y_LABEL_NUMBER, graph_axis_label_colour, SA_CENTER);
00384 
00385         label += this->x_values_increment;
00386         x += x_sep;
00387       }
00388     }
00389 
00390     /* draw lines and dots */
00391     for (int i = 0; i < this->num_dataset; i++) {
00392       if (!HasBit(this->excluded_data, i)) {
00393         /* Centre the dot between the grid lines. */
00394         x = r.left + (x_sep / 2);
00395 
00396         byte colour  = this->colours[i];
00397         uint prev_x = INVALID_DATAPOINT_POS;
00398         uint prev_y = INVALID_DATAPOINT_POS;
00399 
00400         for (int j = 0; j < this->num_on_x_axis; j++) {
00401           OverflowSafeInt64 datapoint = this->cost[i][j];
00402 
00403           if (datapoint != INVALID_DATAPOINT) {
00404             /*
00405              * Check whether we need to reduce the 'accuracy' of the
00406              * datapoint value and the highest value to splut overflows.
00407              * And when 'drawing' 'one million' or 'one million and one'
00408              * there is no significant difference, so the least
00409              * significant bits can just be removed.
00410              *
00411              * If there are more bits needed than would fit in a 32 bits
00412              * integer, so at about 31 bits because of the sign bit, the
00413              * least significant bits are removed.
00414              */
00415             int mult_range = FindLastBit(x_axis_offset) + FindLastBit(abs(datapoint));
00416             int reduce_range = max(mult_range - 31, 0);
00417 
00418             /* Handle negative values differently (don't shift sign) */
00419             if (datapoint < 0) {
00420               datapoint = -(abs(datapoint) >> reduce_range);
00421             } else {
00422               datapoint >>= reduce_range;
00423             }
00424 
00425             y = r.top + x_axis_offset - (x_axis_offset * datapoint) / (highest_value >> reduce_range);
00426 
00427             /* Draw the point. */
00428             GfxFillRect(x - 1, y - 1, x + 1, y + 1, colour);
00429 
00430             /* Draw the line connected to the previous point. */
00431             if (prev_x != INVALID_DATAPOINT_POS) GfxDrawLine(prev_x, prev_y, x, y, colour);
00432 
00433             prev_x = x;
00434             prev_y = y;
00435           } else {
00436             prev_x = INVALID_DATAPOINT_POS;
00437             prev_y = INVALID_DATAPOINT_POS;
00438           }
00439 
00440           x += x_sep;
00441         }
00442       }
00443     }
00444   }
00445 
00446 
00447   BaseGraphWindow(int widget, bool has_negative_values, StringID format_str_y_axis) :
00448       Window(), has_negative_values(has_negative_values),
00449       format_str_y_axis(format_str_y_axis)
00450   {
00451     SetWindowDirty(WC_GRAPH_LEGEND, 0);
00452     this->num_vert_lines = 24;
00453     this->graph_widget = widget;
00454   }
00455 
00456   void InitializeWindow(const WindowDesc *desc, WindowNumber number)
00457   {
00458     /* Initialise the dataset */
00459     this->UpdateStatistics(true);
00460 
00461     this->InitNested(desc, number);
00462   }
00463 
00464 public:
00465   virtual void OnPaint()
00466   {
00467     this->DrawWidgets();
00468   }
00469 
00470   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
00471   {
00472     if (widget != this->graph_widget) return;
00473 
00474     uint x_label_width = 0;
00475 
00476     if (this->month != 0xFF) {
00477       byte month = this->month;
00478       Year year  = this->year;
00479       for (int i = 0; i < this->num_on_x_axis; i++) {
00480         SetDParam(0, month + STR_MONTH_ABBREV_JAN);
00481         SetDParam(1, month + STR_MONTH_ABBREV_JAN + 2);
00482         SetDParam(2, year);
00483         x_label_width = max(x_label_width, GetStringBoundingBox(month == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH).width);
00484 
00485         month += 3;
00486         if (month >= 12) {
00487           month = 0;
00488           year++;
00489         }
00490       }
00491     } else {
00492       /* Draw the label under the data point rather than on the grid line. */
00493       SetDParam(0, this->x_values_start + this->num_on_x_axis * this->x_values_increment);
00494       x_label_width = GetStringBoundingBox(STR_GRAPH_Y_LABEL_NUMBER).width;
00495     }
00496 
00497     SetDParam(0, this->format_str_y_axis);
00498     SetDParam(1, INT64_MAX);
00499     uint y_label_width = GetStringBoundingBox(STR_GRAPH_Y_LABEL).width;
00500 
00501     size->width  = max<uint>(size->width,  5 + y_label_width + this->num_on_x_axis * (x_label_width + 5) + 9);
00502     size->height = max<uint>(size->height, 5 + (1 + GRAPH_NUM_LINES_Y * 2 + (this->month != 0xFF ? 3 : 1)) * FONT_HEIGHT_SMALL + 4);
00503     size->height = max<uint>(size->height, size->width / 3);
00504   }
00505 
00506   virtual void DrawWidget(const Rect &r, int widget) const
00507   {
00508     if (widget != this->graph_widget) return;
00509 
00510     DrawGraph(r);
00511   }
00512 
00513   virtual OverflowSafeInt64 GetGraphData(const Company *c, int j)
00514   {
00515     return INVALID_DATAPOINT;
00516   }
00517 
00518   virtual void OnClick(Point pt, int widget, int click_count)
00519   {
00520     /* Clicked on legend? */
00521     if (widget == BGW_KEY_BUTTON) ShowGraphLegend();
00522   }
00523 
00524   virtual void OnTick()
00525   {
00526     this->UpdateStatistics(false);
00527   }
00528 
00529   virtual void OnInvalidateData(int data)
00530   {
00531     this->UpdateStatistics(true);
00532   }
00533 
00538   void UpdateStatistics(bool initialize)
00539   {
00540     uint excluded_companies = _legend_excluded_companies;
00541 
00542     /* Exclude the companies which aren't valid */
00543     for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) {
00544       if (!Company::IsValidID(c)) SetBit(excluded_companies, c);
00545     }
00546 
00547     byte nums = 0;
00548     const Company *c;
00549     FOR_ALL_COMPANIES(c) {
00550       nums = min(this->num_vert_lines, max(nums, c->num_valid_stat_ent));
00551     }
00552 
00553     int mo = (_cur_month / 3 - nums) * 3;
00554     int yr = _cur_year;
00555     while (mo < 0) {
00556       yr--;
00557       mo += 12;
00558     }
00559 
00560     if (!initialize && this->excluded_data == excluded_companies && this->num_on_x_axis == nums &&
00561         this->year == yr && this->month == mo) {
00562       /* There's no reason to get new stats */
00563       return;
00564     }
00565 
00566     this->excluded_data = excluded_companies;
00567     this->num_on_x_axis = nums;
00568     this->year = yr;
00569     this->month = mo;
00570 
00571     int numd = 0;
00572     for (CompanyID k = COMPANY_FIRST; k < MAX_COMPANIES; k++) {
00573       c = Company::GetIfValid(k);
00574       if (c != NULL) {
00575         this->colours[numd] = _colour_gradient[c->colour][6];
00576         for (int j = this->num_on_x_axis, i = 0; --j >= 0;) {
00577           this->cost[numd][i] = (j >= c->num_valid_stat_ent) ? INVALID_DATAPOINT : GetGraphData(c, j);
00578           i++;
00579         }
00580       }
00581       numd++;
00582     }
00583 
00584     this->num_dataset = numd;
00585   }
00586 };
00587 
00588 
00589 /********************/
00590 /* OPERATING PROFIT */
00591 /********************/
00592 
00593 struct OperatingProfitGraphWindow : BaseGraphWindow {
00594   OperatingProfitGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
00595       BaseGraphWindow(BGW_BACKGROUND, true, STR_JUST_CURRCOMPACT)
00596   {
00597     this->InitializeWindow(desc, window_number);
00598   }
00599 
00600   virtual OverflowSafeInt64 GetGraphData(const Company *c, int j)
00601   {
00602     return c->old_economy[j].income + c->old_economy[j].expenses;
00603   }
00604 };
00605 
00606 static const NWidgetPart _nested_operating_profit_widgets[] = {
00607   NWidget(NWID_HORIZONTAL),
00608     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00609     NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_GRAPH_OPERATING_PROFIT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00610     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, BGW_KEY_BUTTON), SetMinimalSize(50, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
00611     NWidget(WWT_SHADEBOX, COLOUR_GREY),
00612     NWidget(WWT_STICKYBOX, COLOUR_GREY),
00613   EndContainer(),
00614   NWidget(WWT_PANEL, COLOUR_GREY, BGW_BACKGROUND), SetMinimalSize(576, 160), EndContainer(),
00615 };
00616 
00617 static const WindowDesc _operating_profit_desc(
00618   WDP_AUTO, 0, 0,
00619   WC_OPERATING_PROFIT, WC_NONE,
00620   WDF_UNCLICK_BUTTONS,
00621   _nested_operating_profit_widgets, lengthof(_nested_operating_profit_widgets)
00622 );
00623 
00624 
00625 void ShowOperatingProfitGraph()
00626 {
00627   AllocateWindowDescFront<OperatingProfitGraphWindow>(&_operating_profit_desc, 0);
00628 }
00629 
00630 
00631 /****************/
00632 /* INCOME GRAPH */
00633 /****************/
00634 
00635 struct IncomeGraphWindow : BaseGraphWindow {
00636   IncomeGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
00637       BaseGraphWindow(BGW_BACKGROUND, false, STR_JUST_CURRCOMPACT)
00638   {
00639     this->InitializeWindow(desc, window_number);
00640   }
00641 
00642   virtual OverflowSafeInt64 GetGraphData(const Company *c, int j)
00643   {
00644     return c->old_economy[j].income;
00645   }
00646 };
00647 
00648 static const NWidgetPart _nested_income_graph_widgets[] = {
00649   NWidget(NWID_HORIZONTAL),
00650     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00651     NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_GRAPH_INCOME_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00652     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, BGW_KEY_BUTTON), SetMinimalSize(50, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
00653     NWidget(WWT_SHADEBOX, COLOUR_GREY),
00654     NWidget(WWT_STICKYBOX, COLOUR_GREY),
00655   EndContainer(),
00656   NWidget(WWT_PANEL, COLOUR_GREY, BGW_BACKGROUND), SetMinimalSize(576, 128), EndContainer(),
00657 };
00658 
00659 
00660 static const WindowDesc _income_graph_desc(
00661   WDP_AUTO, 0, 0,
00662   WC_INCOME_GRAPH, WC_NONE,
00663   WDF_UNCLICK_BUTTONS,
00664   _nested_income_graph_widgets, lengthof(_nested_income_graph_widgets)
00665 );
00666 
00667 void ShowIncomeGraph()
00668 {
00669   AllocateWindowDescFront<IncomeGraphWindow>(&_income_graph_desc, 0);
00670 }
00671 
00672 /*******************/
00673 /* DELIVERED CARGO */
00674 /*******************/
00675 
00676 struct DeliveredCargoGraphWindow : BaseGraphWindow {
00677   DeliveredCargoGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
00678       BaseGraphWindow(BGW_BACKGROUND, false, STR_JUST_COMMA)
00679   {
00680     this->InitializeWindow(desc, window_number);
00681   }
00682 
00683   virtual OverflowSafeInt64 GetGraphData(const Company *c, int j)
00684   {
00685     return c->old_economy[j].delivered_cargo;
00686   }
00687 };
00688 
00689 static const NWidgetPart _nested_delivered_cargo_graph_widgets[] = {
00690   NWidget(NWID_HORIZONTAL),
00691     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00692     NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_GRAPH_CARGO_DELIVERED_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00693     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, BGW_KEY_BUTTON), SetMinimalSize(50, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
00694     NWidget(WWT_SHADEBOX, COLOUR_GREY),
00695     NWidget(WWT_STICKYBOX, COLOUR_GREY),
00696   EndContainer(),
00697   NWidget(WWT_PANEL, COLOUR_GREY, BGW_BACKGROUND), SetMinimalSize(576, 128), EndContainer(),
00698 };
00699 
00700 static const WindowDesc _delivered_cargo_graph_desc(
00701   WDP_AUTO, 0, 0,
00702   WC_DELIVERED_CARGO, WC_NONE,
00703   WDF_UNCLICK_BUTTONS,
00704   _nested_delivered_cargo_graph_widgets, lengthof(_nested_delivered_cargo_graph_widgets)
00705 );
00706 
00707 void ShowDeliveredCargoGraph()
00708 {
00709   AllocateWindowDescFront<DeliveredCargoGraphWindow>(&_delivered_cargo_graph_desc, 0);
00710 }
00711 
00712 /***********************/
00713 /* PERFORMANCE HISTORY */
00714 /***********************/
00715 
00717 enum PerformanceHistoryGraphWidgets {
00718   PHW_KEY,
00719   PHW_DETAILED_PERFORMANCE,
00720   PHW_BACKGROUND,
00721 };
00722 
00723 struct PerformanceHistoryGraphWindow : BaseGraphWindow {
00724   PerformanceHistoryGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
00725       BaseGraphWindow(PHW_BACKGROUND, false, STR_JUST_COMMA)
00726   {
00727     this->InitializeWindow(desc, window_number);
00728   }
00729 
00730   virtual OverflowSafeInt64 GetGraphData(const Company *c, int j)
00731   {
00732     return c->old_economy[j].performance_history;
00733   }
00734 
00735   virtual void OnClick(Point pt, int widget, int click_count)
00736   {
00737     if (widget == PHW_DETAILED_PERFORMANCE) ShowPerformanceRatingDetail();
00738     this->BaseGraphWindow::OnClick(pt, widget, click_count);
00739   }
00740 };
00741 
00742 static const NWidgetPart _nested_performance_history_widgets[] = {
00743   NWidget(NWID_HORIZONTAL),
00744     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00745     NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_GRAPH_COMPANY_PERFORMANCE_RATINGS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00746     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, PHW_DETAILED_PERFORMANCE), SetMinimalSize(50, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_PERFORMANCE_DETAIL_KEY, STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP),
00747     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, PHW_KEY), SetMinimalSize(50, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
00748     NWidget(WWT_SHADEBOX, COLOUR_GREY),
00749     NWidget(WWT_STICKYBOX, COLOUR_GREY),
00750   EndContainer(),
00751   NWidget(WWT_PANEL, COLOUR_GREY, PHW_BACKGROUND), SetMinimalSize(576, 224), EndContainer(),
00752 };
00753 
00754 static const WindowDesc _performance_history_desc(
00755   WDP_AUTO, 0, 0,
00756   WC_PERFORMANCE_HISTORY, WC_NONE,
00757   WDF_UNCLICK_BUTTONS,
00758   _nested_performance_history_widgets, lengthof(_nested_performance_history_widgets)
00759 );
00760 
00761 void ShowPerformanceHistoryGraph()
00762 {
00763   AllocateWindowDescFront<PerformanceHistoryGraphWindow>(&_performance_history_desc, 0);
00764 }
00765 
00766 /*****************/
00767 /* COMPANY VALUE */
00768 /*****************/
00769 
00770 struct CompanyValueGraphWindow : BaseGraphWindow {
00771   CompanyValueGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
00772       BaseGraphWindow(BGW_BACKGROUND, false, STR_JUST_CURRCOMPACT)
00773   {
00774     this->InitializeWindow(desc, window_number);
00775   }
00776 
00777   virtual OverflowSafeInt64 GetGraphData(const Company *c, int j)
00778   {
00779     return c->old_economy[j].company_value;
00780   }
00781 };
00782 
00783 static const NWidgetPart _nested_company_value_graph_widgets[] = {
00784   NWidget(NWID_HORIZONTAL),
00785     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00786     NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_GRAPH_COMPANY_VALUES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00787     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, BGW_KEY_BUTTON), SetMinimalSize(50, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
00788     NWidget(WWT_SHADEBOX, COLOUR_GREY),
00789     NWidget(WWT_STICKYBOX, COLOUR_GREY),
00790   EndContainer(),
00791   NWidget(WWT_PANEL, COLOUR_GREY, BGW_BACKGROUND), SetMinimalSize(576, 224), EndContainer(),
00792 };
00793 
00794 static const WindowDesc _company_value_graph_desc(
00795   WDP_AUTO, 0, 0,
00796   WC_COMPANY_VALUE, WC_NONE,
00797   WDF_UNCLICK_BUTTONS,
00798   _nested_company_value_graph_widgets, lengthof(_nested_company_value_graph_widgets)
00799 );
00800 
00801 void ShowCompanyValueGraph()
00802 {
00803   AllocateWindowDescFront<CompanyValueGraphWindow>(&_company_value_graph_desc, 0);
00804 }
00805 
00806 /*****************/
00807 /* PAYMENT RATES */
00808 /*****************/
00809 
00811 enum CargoPaymentRatesWidgets {
00812   CPW_BACKGROUND,
00813   CPW_HEADER,
00814   CPW_GRAPH,
00815   CPW_FOOTER,
00816   CPW_CARGO_FIRST,
00817 };
00818 
00819 struct PaymentRatesGraphWindow : BaseGraphWindow {
00820   PaymentRatesGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
00821       BaseGraphWindow(CPW_GRAPH, false, STR_JUST_CURRCOMPACT)
00822   {
00823     this->num_on_x_axis = 20;
00824     this->num_vert_lines = 20;
00825     this->month = 0xFF;
00826     this->x_values_start     = 10;
00827     this->x_values_increment = 10;
00828 
00829     /* Initialise the dataset */
00830     this->OnHundredthTick();
00831 
00832     this->InitNested(desc, window_number);
00833 
00834     int i = 0;
00835     const CargoSpec *cs;
00836     FOR_ALL_CARGOSPECS(cs) {
00837       this->SetWidgetLoweredState(CPW_CARGO_FIRST + cs->Index(), !HasBit(_legend_excluded_cargo, i));
00838       i++;
00839     }
00840   }
00841 
00842   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
00843   {
00844     if (widget < CPW_CARGO_FIRST) {
00845       BaseGraphWindow::UpdateWidgetSize(widget, size, padding, fill, resize);
00846       return;
00847     }
00848 
00849     const CargoSpec *cs = CargoSpec::Get(widget - CPW_CARGO_FIRST);
00850     SetDParam(0, cs->name);
00851     Dimension d = GetStringBoundingBox(STR_GRAPH_CARGO_PAYMENT_CARGO);
00852     d.width += 14; // colour field
00853     d.width += WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
00854     d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
00855     *size = maxdim(d, *size);
00856   }
00857 
00858   virtual void DrawWidget(const Rect &r, int widget) const
00859   {
00860     if (widget < CPW_CARGO_FIRST) {
00861       BaseGraphWindow::DrawWidget(r, widget);
00862       return;
00863     }
00864 
00865     const CargoSpec *cs = CargoSpec::Get(widget - CPW_CARGO_FIRST);
00866     bool rtl = _dynlang.text_dir == TD_RTL;
00867 
00868     /* Since the buttons have no text, no images,
00869      * both the text and the coloured box have to be manually painted.
00870      * clk_dif will move one pixel down and one pixel to the right
00871      * when the button is clicked */
00872     byte clk_dif = this->IsWidgetLowered(widget) ? 1 : 0;
00873     int x = r.left + WD_FRAMERECT_LEFT;
00874     int y = r.top;
00875 
00876     int rect_x = clk_dif + (rtl ? r.right - 12 : r.left + WD_FRAMERECT_LEFT);
00877 
00878     GfxFillRect(rect_x, y + clk_dif, rect_x + 8, y + 5 + clk_dif, 0);
00879     GfxFillRect(rect_x + 1, y + 1 + clk_dif, rect_x + 7, y + 4 + clk_dif, cs->legend_colour);
00880     SetDParam(0, cs->name);
00881     DrawString(rtl ? r.left : x + 14 + clk_dif, (rtl ? r.right - 14 + clk_dif : r.right), y + clk_dif, STR_GRAPH_CARGO_PAYMENT_CARGO);
00882   }
00883 
00884   virtual void OnClick(Point pt, int widget, int click_count)
00885   {
00886     if (widget >= CPW_CARGO_FIRST) {
00887       int i = 0;
00888       const CargoSpec *cs;
00889       FOR_ALL_CARGOSPECS(cs) {
00890         if (cs->Index() + CPW_CARGO_FIRST == widget) break;
00891         i++;
00892       }
00893 
00894       ToggleBit(_legend_excluded_cargo, i);
00895       this->ToggleWidgetLoweredState(widget);
00896       this->excluded_data = _legend_excluded_cargo;
00897       this->SetDirty();
00898     }
00899   }
00900 
00901   virtual void OnTick()
00902   {
00903     /* Override default OnTick */
00904   }
00905 
00906   virtual void OnInvalidateData(int data)
00907   {
00908     this->OnHundredthTick();
00909   }
00910 
00911   virtual void OnHundredthTick()
00912   {
00913     this->excluded_data = _legend_excluded_cargo;
00914 
00915     int i = 0;
00916     const CargoSpec *cs;
00917     FOR_ALL_CARGOSPECS(cs) {
00918       this->colours[i] = cs->legend_colour;
00919       for (uint j = 0; j != 20; j++) {
00920         this->cost[i][j] = GetTransportedGoodsIncome(10, 20, j * 4 + 4, cs->Index());
00921       }
00922 
00923       i++;
00924     }
00925     this->num_dataset = i;
00926   }
00927 };
00928 
00930 static NWidgetBase *MakeCargoButtons(int *biggest_index)
00931 {
00932   NWidgetVertical *ver = new NWidgetVertical;
00933 
00934   const CargoSpec *cs;
00935   FOR_ALL_CARGOSPECS(cs) {
00936     *biggest_index = CPW_CARGO_FIRST + cs->Index();
00937     NWidgetBackground *leaf = new NWidgetBackground(WWT_PANEL, COLOUR_ORANGE, *biggest_index, NULL);
00938     leaf->tool_tip = STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO;
00939     leaf->SetFill(1, 0);
00940     leaf->SetLowered(true);
00941     ver->Add(leaf);
00942   }
00943   return ver;
00944 }
00945 
00946 
00947 static const NWidgetPart _nested_cargo_payment_rates_widgets[] = {
00948   NWidget(NWID_HORIZONTAL),
00949     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00950     NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_GRAPH_CARGO_PAYMENT_RATES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00951     NWidget(WWT_SHADEBOX, COLOUR_GREY),
00952     NWidget(WWT_STICKYBOX, COLOUR_GREY),
00953   EndContainer(),
00954   NWidget(WWT_PANEL, COLOUR_GREY, CPW_BACKGROUND), SetMinimalSize(568, 128), SetResize(0, 1),
00955     NWidget(NWID_VERTICAL),
00956       NWidget(NWID_HORIZONTAL),
00957         NWidget(NWID_SPACER), SetFill(1, 0),
00958         NWidget(WWT_TEXT, COLOUR_GREY, CPW_HEADER), SetMinimalSize(0, 6), SetPadding(2, 0, 2, 0), SetDataTip(STR_GRAPH_CARGO_PAYMENT_RATES_TITLE, STR_NULL),
00959         NWidget(NWID_SPACER), SetFill(1, 0),
00960       EndContainer(),
00961       NWidget(NWID_HORIZONTAL),
00962         NWidget(WWT_EMPTY, COLOUR_GREY, CPW_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1),
00963         NWidget(NWID_VERTICAL),
00964           NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 0),
00965             NWidgetFunction(MakeCargoButtons),
00966           NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
00967         EndContainer(),
00968         NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1),
00969       EndContainer(),
00970       NWidget(NWID_HORIZONTAL),
00971         NWidget(NWID_SPACER), SetFill(1, 0),
00972         NWidget(WWT_TEXT, COLOUR_GREY, CPW_FOOTER), SetMinimalSize(0, 6), SetPadding(2, 0, 2, 0), SetDataTip(STR_GRAPH_CARGO_PAYMENT_RATES_X_LABEL, STR_NULL),
00973         NWidget(NWID_SPACER), SetFill(1, 0),
00974       EndContainer(),
00975     EndContainer(),
00976   EndContainer(),
00977 };
00978 
00979 static const WindowDesc _cargo_payment_rates_desc(
00980   WDP_AUTO, 0, 0,
00981   WC_PAYMENT_RATES, WC_NONE,
00982   0,
00983   _nested_cargo_payment_rates_widgets, lengthof(_nested_cargo_payment_rates_widgets)
00984 );
00985 
00986 
00987 void ShowCargoPaymentRates()
00988 {
00989   AllocateWindowDescFront<PaymentRatesGraphWindow>(&_cargo_payment_rates_desc, 0);
00990 }
00991 
00992 /************************/
00993 /* COMPANY LEAGUE TABLE */
00994 /************************/
00995 
00997 enum CompanyLeagueWidgets {
00998   CLW_BACKGROUND,
00999 };
01000 
01001 static const StringID _performance_titles[] = {
01002   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ENGINEER,
01003   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ENGINEER,
01004   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRAFFIC_MANAGER,
01005   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRAFFIC_MANAGER,
01006   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRANSPORT_COORDINATOR,
01007   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRANSPORT_COORDINATOR,
01008   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ROUTE_SUPERVISOR,
01009   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ROUTE_SUPERVISOR,
01010   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_DIRECTOR,
01011   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_DIRECTOR,
01012   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHIEF_EXECUTIVE,
01013   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHIEF_EXECUTIVE,
01014   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHAIRMAN,
01015   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHAIRMAN,
01016   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_PRESIDENT,
01017   STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TYCOON,
01018 };
01019 
01020 static inline StringID GetPerformanceTitleFromValue(uint value)
01021 {
01022   return _performance_titles[minu(value, 1000) >> 6];
01023 }
01024 
01025 class CompanyLeagueWindow : public Window {
01026 private:
01027   GUIList<const Company*> companies;
01028   uint ordinal_width; 
01029   uint text_width;    
01030 
01034   void BuildCompanyList()
01035   {
01036     if (!this->companies.NeedRebuild()) return;
01037 
01038     this->companies.Clear();
01039 
01040     const Company *c;
01041     FOR_ALL_COMPANIES(c) {
01042       *this->companies.Append() = c;
01043     }
01044 
01045     this->companies.Compact();
01046     this->companies.RebuildDone();
01047   }
01048 
01050   static int CDECL PerformanceSorter(const Company * const *c1, const Company * const *c2)
01051   {
01052     return (*c2)->old_economy[1].performance_history - (*c1)->old_economy[1].performance_history;
01053   }
01054 
01055 public:
01056   CompanyLeagueWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
01057   {
01058     this->InitNested(desc, window_number);
01059     this->companies.ForceRebuild();
01060     this->companies.NeedResort();
01061   }
01062 
01063   virtual void OnPaint()
01064   {
01065     this->BuildCompanyList();
01066     this->companies.Sort(&PerformanceSorter);
01067 
01068     this->DrawWidgets();
01069   }
01070 
01071   virtual void DrawWidget(const Rect &r, int widget) const
01072   {
01073     if (widget != CLW_BACKGROUND) return;
01074 
01075     uint y = r.top + WD_FRAMERECT_TOP;
01076     int icon_y_offset = 1 + (FONT_HEIGHT_NORMAL - 10) / 2;
01077 
01078     bool rtl = _dynlang.text_dir == TD_RTL;
01079     uint ordinal_left  = rtl ? r.right - WD_FRAMERECT_LEFT - this->ordinal_width : r.left + WD_FRAMERECT_LEFT;
01080     uint ordinal_right = rtl ? r.right - WD_FRAMERECT_LEFT : r.left + WD_FRAMERECT_LEFT + this->ordinal_width;
01081     uint icon_left     = r.left + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT + (rtl ? this->text_width : this->ordinal_width);
01082     uint text_left     = rtl ? r.left + WD_FRAMERECT_LEFT : r.right - WD_FRAMERECT_LEFT - this->text_width;
01083     uint text_right    = rtl ? r.left + WD_FRAMERECT_LEFT + this->text_width : r.right - WD_FRAMERECT_LEFT;
01084 
01085     for (uint i = 0; i != this->companies.Length(); i++) {
01086       const Company *c = this->companies[i];
01087       DrawString(ordinal_left, ordinal_right, y, i + STR_ORDINAL_NUMBER_1ST, i == 0 ? TC_WHITE : TC_YELLOW);
01088 
01089       DrawCompanyIcon(c->index, icon_left, y + icon_y_offset);
01090 
01091       SetDParam(0, c->index);
01092       SetDParam(1, c->index);
01093       SetDParam(2, GetPerformanceTitleFromValue(c->old_economy[1].performance_history));
01094       DrawString(text_left, text_right, y, STR_COMPANY_LEAGUE_COMPANY_NAME);
01095       y += FONT_HEIGHT_NORMAL;
01096     }
01097   }
01098 
01099   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
01100   {
01101     if (widget != CLW_BACKGROUND) return;
01102 
01103     this->ordinal_width = 0;
01104     for (uint i = 0; i < MAX_COMPANIES; i++) {
01105       this->ordinal_width = max(this->ordinal_width, GetStringBoundingBox(STR_ORDINAL_NUMBER_1ST + i).width);
01106     }
01107     this->ordinal_width += 5; // Keep some extra spacing
01108 
01109     uint widest_width = 0;
01110     uint widest_title = 0;
01111     for (uint i = 0; i < lengthof(_performance_titles); i++) {
01112       uint width = GetStringBoundingBox(_performance_titles[i]).width;
01113       if (width > widest_width) {
01114         widest_title = i;
01115         widest_width = width;
01116       }
01117     }
01118 
01119     const Company *c;
01120     FOR_ALL_COMPANIES(c) {
01121       SetDParam(0, c->index);
01122       SetDParam(1, c->index);
01123       SetDParam(2, _performance_titles[widest_title]);
01124       widest_width = max(widest_width, GetStringBoundingBox(STR_COMPANY_LEAGUE_COMPANY_NAME).width);
01125     }
01126 
01127     this->text_width = widest_width + 30; // Keep some extra spacing
01128 
01129     size->width = WD_FRAMERECT_LEFT + this->ordinal_width + WD_FRAMERECT_RIGHT + 16 + WD_FRAMERECT_LEFT + this->text_width + WD_FRAMERECT_RIGHT;
01130   }
01131 
01132 
01133   virtual void OnTick()
01134   {
01135     if (this->companies.NeedResort()) {
01136       this->SetDirty();
01137     }
01138   }
01139 
01140   virtual void OnInvalidateData(int data)
01141   {
01142     if (data == 0) {
01143       this->companies.ForceRebuild();
01144     } else {
01145       this->companies.ForceResort();
01146     }
01147   }
01148 };
01149 
01150 static const NWidgetPart _nested_company_league_widgets[] = {
01151   NWidget(NWID_HORIZONTAL),
01152     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
01153     NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_COMPANY_LEAGUE_TABLE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
01154     NWidget(WWT_SHADEBOX, COLOUR_GREY),
01155     NWidget(WWT_STICKYBOX, COLOUR_GREY),
01156   EndContainer(),
01157   NWidget(WWT_PANEL, COLOUR_GREY, CLW_BACKGROUND), SetMinimalSize(400, 0), SetMinimalTextLines(15, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM),
01158 };
01159 
01160 static const WindowDesc _company_league_desc(
01161   WDP_AUTO, 0, 0,
01162   WC_COMPANY_LEAGUE, WC_NONE,
01163   0,
01164   _nested_company_league_widgets, lengthof(_nested_company_league_widgets)
01165 );
01166 
01167 void ShowCompanyLeagueTable()
01168 {
01169   AllocateWindowDescFront<CompanyLeagueWindow>(&_company_league_desc, 0);
01170 }
01171 
01172 /*****************************/
01173 /* PERFORMANCE RATING DETAIL */
01174 /*****************************/
01175 
01177 enum PerformanceRatingDetailsWidgets {
01178   PRW_SCORE_FIRST,
01179   PRW_SCORE_LAST = PRW_SCORE_FIRST + (SCORE_END - SCORE_BEGIN) - 1,
01180 
01181   PRW_COMPANY_FIRST,
01182   PRW_COMPANY_LAST  = PRW_COMPANY_FIRST + MAX_COMPANIES - 1,
01183 };
01184 
01185 struct PerformanceRatingDetailWindow : Window {
01186   static CompanyID company;
01187   int timeout;
01188 
01189   PerformanceRatingDetailWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
01190   {
01191     this->UpdateCompanyStats();
01192 
01193     this->InitNested(desc, window_number);
01194     this->OnInvalidateData(INVALID_COMPANY);
01195   }
01196 
01197   void UpdateCompanyStats()
01198   {
01199     /* Update all company stats with the current data
01200      * (this is because _score_info is not saved to a savegame) */
01201     Company *c;
01202     FOR_ALL_COMPANIES(c) {
01203       UpdateCompanyRatingAndValue(c, false);
01204     }
01205 
01206     this->timeout = DAY_TICKS * 5;
01207   }
01208 
01209   uint score_info_left;
01210   uint score_info_right;
01211   uint bar_left;
01212   uint bar_right;
01213   uint bar_width;
01214   uint bar_height;
01215   uint score_detail_left;
01216   uint score_detail_right;
01217 
01218   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
01219   {
01220     switch (widget) {
01221       case PRW_SCORE_FIRST:
01222         this->bar_height = FONT_HEIGHT_NORMAL + 4;
01223         size->height = this->bar_height + 2 * WD_MATRIX_TOP;
01224 
01225         uint score_info_width = 0;
01226         for (uint i = SCORE_BEGIN; i < SCORE_END; i++) {
01227           score_info_width = max(score_info_width, GetStringBoundingBox(STR_PERFORMANCE_DETAIL_VEHICLES + i).width);
01228         }
01229         SetDParam(0, 1000);
01230         score_info_width += GetStringBoundingBox(STR_BLACK_COMMA).width + WD_FRAMERECT_LEFT;
01231 
01232         SetDParam(0, 100);
01233         this->bar_width = GetStringBoundingBox(STR_PERFORMANCE_DETAIL_PERCENT).width + 20; // Wide bars!
01234 
01235         /* At this number we are roughly at the max; it can become wider,
01236          * but then you need at 1000 times more money. At that time you're
01237          * not that interested anymore in the last few digits anyway. */
01238         uint max = 999999999; // nine 9s
01239         SetDParam(0, max);
01240         SetDParam(1, max);
01241         uint score_detail_width = GetStringBoundingBox(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY).width;
01242 
01243         size->width = 7 + score_info_width + 5 + this->bar_width + 5 + score_detail_width + 7;
01244         uint left  = 7;
01245         uint right = size->width - 7;
01246 
01247         bool rtl = _dynlang.text_dir == TD_RTL;
01248         this->score_info_left  = rtl ? right - score_info_width : left;
01249         this->score_info_right = rtl ? right : left + score_info_width;
01250 
01251         this->score_detail_left  = rtl ? left : right - score_detail_width;
01252         this->score_detail_right = rtl ? left + score_detail_width : right;
01253 
01254         this->bar_left  = left + (rtl ? score_detail_width : score_info_width) + 5;
01255         this->bar_right = this->bar_left + this->bar_width;
01256         break;
01257     }
01258   }
01259 
01260   virtual void OnPaint()
01261   {
01262     /* Draw standard stuff */
01263     this->DrawWidgets();
01264   }
01265 
01266   virtual void DrawWidget(const Rect &r, int widget) const
01267   {
01268     /* No need to draw when there's nothing to draw */
01269     if (this->company == INVALID_COMPANY) return;
01270 
01271     if (IsInsideMM(widget, PRW_COMPANY_FIRST, PRW_COMPANY_LAST + 1)) {
01272       if (this->IsWidgetDisabled(widget)) return;
01273       CompanyID cid = (CompanyID)(widget - PRW_COMPANY_FIRST);
01274       int offset = (cid == this->company) ? 1 : 0;
01275       Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON);
01276       DrawCompanyIcon(cid, (r.left + r.right - sprite_size.width) / 2 + offset, (r.top + r.bottom - sprite_size.height) / 2 + offset);
01277       return;
01278     }
01279 
01280     if (!IsInsideMM(widget, PRW_SCORE_FIRST, PRW_SCORE_LAST + 1)) return;
01281 
01282     ScoreID score_type = (ScoreID)(widget - PRW_SCORE_FIRST);
01283 
01284       /* The colours used to show how the progress is going */
01285     int colour_done = _colour_gradient[COLOUR_GREEN][4];
01286     int colour_notdone = _colour_gradient[COLOUR_RED][4];
01287 
01288     /* Draw all the score parts */
01289     int val    = _score_part[company][score_type];
01290     int needed = _score_info[score_type].needed;
01291     int score  = _score_info[score_type].score;
01292 
01293     /* SCORE_TOTAL has his own rules ;) */
01294     if (score_type == SCORE_TOTAL) {
01295       for (ScoreID i = SCORE_BEGIN; i < SCORE_END; i++) score += _score_info[i].score;
01296       needed = SCORE_MAX;
01297     }
01298 
01299     uint bar_top  = r.top + WD_MATRIX_TOP;
01300     uint text_top = bar_top + 2;
01301 
01302     DrawString(this->score_info_left, this->score_info_right, text_top, STR_PERFORMANCE_DETAIL_VEHICLES + score_type);
01303 
01304     /* Draw the score */
01305     SetDParam(0, score);
01306     DrawString(this->score_info_left, this->score_info_right, text_top, STR_BLACK_COMMA, TC_FROMSTRING, SA_RIGHT);
01307 
01308     /* Calculate the %-bar */
01309     uint x = Clamp(val, 0, needed) * this->bar_width / needed;
01310     bool rtl = _dynlang.text_dir == TD_RTL;
01311     if (rtl) {
01312       x = this->bar_right - x;
01313     } else {
01314       x = this->bar_left + x;
01315     }
01316 
01317     /* Draw the bar */
01318     if (x != this->bar_left)  GfxFillRect(this->bar_left, bar_top, x, bar_top + this->bar_height, rtl ? colour_notdone : colour_done);
01319     if (x != this->bar_right) GfxFillRect(x, bar_top, this->bar_right, bar_top + this->bar_height, rtl ? colour_done : colour_notdone);
01320 
01321     /* Draw it */
01322     SetDParam(0, Clamp(val, 0, needed) * 100 / needed);
01323     DrawString(this->bar_left, this->bar_right, text_top, STR_PERFORMANCE_DETAIL_PERCENT, TC_FROMSTRING, SA_CENTER);
01324 
01325     /* SCORE_LOAN is inversed */
01326     if (score_type == SCORE_LOAN) val = needed - val;
01327 
01328     /* Draw the amount we have against what is needed
01329      * For some of them it is in currency format */
01330     SetDParam(0, val);
01331     SetDParam(1, needed);
01332     switch (score_type) {
01333       case SCORE_MIN_PROFIT:
01334       case SCORE_MIN_INCOME:
01335       case SCORE_MAX_INCOME:
01336       case SCORE_MONEY:
01337       case SCORE_LOAN:
01338         DrawString(this->score_detail_left, this->score_detail_right, text_top, STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY);
01339         break;
01340       default:
01341         DrawString(this->score_detail_left, this->score_detail_right, text_top, STR_PERFORMANCE_DETAIL_AMOUNT_INT);
01342     }
01343   }
01344 
01345   virtual void OnClick(Point pt, int widget, int click_count)
01346   {
01347     /* Check which button is clicked */
01348     if (IsInsideMM(widget, PRW_COMPANY_FIRST, PRW_COMPANY_LAST + 1)) {
01349       /* Is it no on disable? */
01350       if (!this->IsWidgetDisabled(widget)) {
01351         this->RaiseWidget(this->company + PRW_COMPANY_FIRST);
01352         this->company = (CompanyID)(widget - PRW_COMPANY_FIRST);
01353         this->LowerWidget(this->company + PRW_COMPANY_FIRST);
01354         this->SetDirty();
01355       }
01356     }
01357   }
01358 
01359   virtual void OnTick()
01360   {
01361     if (_pause_mode != PM_UNPAUSED) return;
01362 
01363     /* Update the company score every 5 days */
01364     if (--this->timeout == 0) {
01365       this->UpdateCompanyStats();
01366       this->SetDirty();
01367     }
01368   }
01369 
01374   virtual void OnInvalidateData(int data)
01375   {
01376     /* Disable the companies who are not active */
01377     for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) {
01378       this->SetWidgetDisabledState(i + PRW_COMPANY_FIRST, !Company::IsValidID(i));
01379     }
01380 
01381     /* Check if the currently selected company is still active. */
01382     if (this->company != INVALID_COMPANY && !Company::IsValidID(this->company)) {
01383       /* Raise the widget for the previous selection. */
01384       this->RaiseWidget(this->company + PRW_COMPANY_FIRST);
01385       this->company = INVALID_COMPANY;
01386     }
01387 
01388     if (this->company == INVALID_COMPANY) {
01389       const Company *c;
01390       FOR_ALL_COMPANIES(c) {
01391         this->company = c->index;
01392         break;
01393       }
01394     }
01395 
01396     /* Make sure the widget is lowered */
01397     this->LowerWidget(this->company + PRW_COMPANY_FIRST);
01398   }
01399 };
01400 
01401 CompanyID PerformanceRatingDetailWindow::company = INVALID_COMPANY;
01402 
01408 static NWidgetBase *MakePerformanceDetailPanels(int *biggest_index)
01409 {
01410   const StringID performance_tips[] = {
01411     STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP,
01412     STR_PERFORMANCE_DETAIL_STATIONS_TOOLTIP,
01413     STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP,
01414     STR_PERFORMANCE_DETAIL_MIN_INCOME_TOOLTIP,
01415     STR_PERFORMANCE_DETAIL_MAX_INCOME_TOOLTIP,
01416     STR_PERFORMANCE_DETAIL_DELIVERED_TOOLTIP,
01417     STR_PERFORMANCE_DETAIL_CARGO_TOOLTIP,
01418     STR_PERFORMANCE_DETAIL_MONEY_TOOLTIP,
01419     STR_PERFORMANCE_DETAIL_LOAN_TOOLTIP,
01420     STR_PERFORMANCE_DETAIL_TOTAL_TOOLTIP,
01421   };
01422 
01423   assert_compile(lengthof(performance_tips) == SCORE_END - SCORE_BEGIN);
01424 
01425   NWidgetVertical *vert = new NWidgetVertical(NC_EQUALSIZE);
01426   for (int widnum = PRW_SCORE_FIRST; widnum <= PRW_SCORE_LAST; widnum++) {
01427     NWidgetBackground *panel = new NWidgetBackground(WWT_PANEL, COLOUR_GREY, widnum);
01428     panel->SetFill(1, 1);
01429     panel->SetDataTip(0x0, performance_tips[widnum - PRW_SCORE_FIRST]);
01430     vert->Add(panel);
01431   }
01432   *biggest_index = PRW_SCORE_LAST;
01433   return vert;
01434 }
01435 
01442 static NWidgetBase *MakeCompanyButtonRows(int *biggest_index)
01443 {
01444   static const int MAX_LENGTH = 8; // Maximal number of company buttons in one row.
01445   NWidgetVertical *vert = NULL; // Storage for all rows.
01446   NWidgetHorizontal *hor = NULL; // Storage for buttons in one row.
01447   int hor_length = 0;
01448 
01449   Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON);
01450   sprite_size.width  += WD_MATRIX_LEFT + WD_MATRIX_RIGHT;
01451   sprite_size.height += WD_MATRIX_TOP + WD_MATRIX_BOTTOM + 1; // 1 for the 'offset' of being pressed
01452 
01453   for (int widnum = PRW_COMPANY_FIRST; widnum <= PRW_COMPANY_LAST; widnum++) {
01454     /* Ensure there is room in 'hor' for another button. */
01455     if (hor_length == MAX_LENGTH) {
01456       if (vert == NULL) vert = new NWidgetVertical();
01457       vert->Add(hor);
01458       hor = NULL;
01459       hor_length = 0;
01460     }
01461     if (hor == NULL) {
01462       hor = new NWidgetHorizontal();
01463       hor_length = 0;
01464     }
01465 
01466     NWidgetBackground *panel = new NWidgetBackground(WWT_PANEL, COLOUR_GREY, widnum);
01467     panel->SetMinimalSize(sprite_size.width, sprite_size.height);
01468     panel->SetFill(1, 0);
01469     panel->SetDataTip(0x0, STR_PERFORMANCE_DETAIL_SELECT_COMPANY_TOOLTIP);
01470     hor->Add(panel);
01471     hor_length++;
01472   }
01473   *biggest_index = PRW_COMPANY_LAST;
01474   if (vert == NULL) return hor; // All buttons fit in a single row.
01475 
01476   if (hor_length > 0 && hor_length < MAX_LENGTH) {
01477     /* Last row is partial, add a spacer at the end to force all buttons to the left. */
01478     NWidgetSpacer *spc = new NWidgetSpacer(0, 0);
01479     spc->SetMinimalSize(sprite_size.width, sprite_size.height);
01480     spc->SetFill(1, 0);
01481     hor->Add(spc);
01482   }
01483   if (hor != NULL) vert->Add(hor);
01484   return vert;
01485 }
01486 
01487 static const NWidgetPart _nested_performance_rating_detail_widgets[] = {
01488   NWidget(NWID_HORIZONTAL),
01489     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
01490     NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_PERFORMANCE_DETAIL, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
01491     NWidget(WWT_SHADEBOX, COLOUR_GREY),
01492     NWidget(WWT_STICKYBOX, COLOUR_GREY),
01493   EndContainer(),
01494   NWidget(WWT_PANEL, COLOUR_GREY),
01495     NWidgetFunction(MakeCompanyButtonRows), SetPadding(0, 1, 1, 2),
01496   EndContainer(),
01497   NWidgetFunction(MakePerformanceDetailPanels),
01498 };
01499 
01500 static const WindowDesc _performance_rating_detail_desc(
01501   WDP_AUTO, 0, 0,
01502   WC_PERFORMANCE_DETAIL, WC_NONE,
01503   0,
01504   _nested_performance_rating_detail_widgets, lengthof(_nested_performance_rating_detail_widgets)
01505 );
01506 
01507 void ShowPerformanceRatingDetail()
01508 {
01509   AllocateWindowDescFront<PerformanceRatingDetailWindow>(&_performance_rating_detail_desc, 0);
01510 }

Generated on Mon Aug 30 19:36:54 2010 for OpenTTD by  doxygen 1.6.1