src/graph_gui.cpp
branchnoai
changeset 10645 8cbdb511a674
parent 10513 33cb70ff2f5d
child 10715 6bdf79ffb022
equal deleted inserted replaced
10644:6c4314786d68 10645:8cbdb511a674
    22 
    22 
    23 /* Bitmasks of player and cargo indices that shouldn't be drawn. */
    23 /* Bitmasks of player and cargo indices that shouldn't be drawn. */
    24 static uint _legend_excluded_players;
    24 static uint _legend_excluded_players;
    25 static uint _legend_excluded_cargo;
    25 static uint _legend_excluded_cargo;
    26 
    26 
    27 /************************/
       
    28 /* GENERIC GRAPH DRAWER */
       
    29 /************************/
       
    30 
       
    31 enum {
       
    32 	GRAPH_MAX_DATASETS = 32,
       
    33 	GRAPH_AXIS_LABEL_COLOUR = TC_BLACK,
       
    34 	GRAPH_AXIS_LINE_COLOUR  = 215,
       
    35 
       
    36 	GRAPH_X_POSITION_BEGINNING  = 44,  ///< Start the graph 44 pixels from gw->left
       
    37 	GRAPH_X_POSITION_SEPARATION = 22,  ///< There are 22 pixels between each X value
       
    38 
       
    39 	GRAPH_NUM_LINES_Y = 9, ///< How many horizontal lines to draw.
       
    40 	/* 9 is convenient as that means the distance between them is the height of the graph / 8,
       
    41 	 * which is the same
       
    42 	 * as height >> 3. */
       
    43 };
       
    44 
       
    45 /* Apparently these don't play well with enums. */
    27 /* Apparently these don't play well with enums. */
    46 static const OverflowSafeInt64 INVALID_DATAPOINT(INT64_MAX); // Value used for a datapoint that shouldn't be drawn.
    28 static const OverflowSafeInt64 INVALID_DATAPOINT(INT64_MAX); // Value used for a datapoint that shouldn't be drawn.
    47 static const uint INVALID_DATAPOINT_POS = UINT_MAX;  // Used to determine if the previous point was drawn.
    29 static const uint INVALID_DATAPOINT_POS = UINT_MAX;  // Used to determine if the previous point was drawn.
    48 
    30 
    49 struct GraphDrawer {
       
    50 	uint excluded_data; ///< bitmask of the datasets that shouldn't be displayed.
       
    51 	byte num_dataset;
       
    52 	byte num_on_x_axis;
       
    53 	bool has_negative_values;
       
    54 	byte num_vert_lines;
       
    55 
       
    56 	/* The starting month and year that values are plotted against. If month is
       
    57 	 * 0xFF, use x_values_start and x_values_increment below instead. */
       
    58 	byte month;
       
    59 	Year year;
       
    60 
       
    61 	/* These values are used if the graph is being plotted against values
       
    62 	 * rather than the dates specified by month and year. */
       
    63 	uint16 x_values_start;
       
    64 	uint16 x_values_increment;
       
    65 
       
    66 	int left, top;  ///< Where to start drawing the graph, in pixels.
       
    67 	uint height;    ///< The height of the graph in pixels.
       
    68 	StringID format_str_y_axis;
       
    69 	byte colors[GRAPH_MAX_DATASETS];
       
    70 	OverflowSafeInt64 cost[GRAPH_MAX_DATASETS][24]; ///< last 2 years
       
    71 };
       
    72 
       
    73 static void DrawGraph(const GraphDrawer *gw)
       
    74 {
       
    75 	uint x, y;                       ///< Reused whenever x and y coordinates are needed.
       
    76 	OverflowSafeInt64 highest_value; ///< Highest value to be drawn.
       
    77 	int x_axis_offset;               ///< Distance from the top of the graph to the x axis.
       
    78 
       
    79 	/* the colors and cost array of GraphDrawer must accomodate
       
    80 	 * both values for cargo and players. So if any are higher, quit */
       
    81 	assert(GRAPH_MAX_DATASETS >= (int)NUM_CARGO && GRAPH_MAX_DATASETS >= (int)MAX_PLAYERS);
       
    82 	assert(gw->num_vert_lines > 0);
       
    83 
       
    84 	byte grid_colour = _colour_gradient[14][4];
       
    85 
       
    86 	/* The coordinates of the opposite edges of the graph. */
       
    87 	int bottom = gw->top + gw->height - 1;
       
    88 	int right  = gw->left + GRAPH_X_POSITION_BEGINNING + gw->num_vert_lines * GRAPH_X_POSITION_SEPARATION - 1;
       
    89 
       
    90 	/* Draw the vertical grid lines. */
       
    91 
       
    92 	/* Don't draw the first line, as that's where the axis will be. */
       
    93 	x = gw->left + GRAPH_X_POSITION_BEGINNING + GRAPH_X_POSITION_SEPARATION;
       
    94 
       
    95 	for (int i = 0; i < gw->num_vert_lines; i++) {
       
    96 		GfxFillRect(x, gw->top, x, bottom, grid_colour);
       
    97 		x += GRAPH_X_POSITION_SEPARATION;
       
    98 	}
       
    99 
       
   100 	/* Draw the horizontal grid lines. */
       
   101 	x = gw->left + GRAPH_X_POSITION_BEGINNING;
       
   102 	y = gw->height + gw->top;
       
   103 
       
   104 	for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
       
   105 		GfxFillRect(x, y, right, y, grid_colour);
       
   106 		y -= (gw->height / (GRAPH_NUM_LINES_Y - 1));
       
   107 	}
       
   108 
       
   109 	/* Draw the y axis. */
       
   110 	GfxFillRect(x, gw->top, x, bottom, GRAPH_AXIS_LINE_COLOUR);
       
   111 
       
   112 	/* Find the distance from the top of the graph to the x axis. */
       
   113 	x_axis_offset = gw->height;
       
   114 
       
   115 	/* The graph is currently symmetrical about the x axis. */
       
   116 	if (gw->has_negative_values) x_axis_offset /= 2;
       
   117 
       
   118 	/* Draw the x axis. */
       
   119 	y = x_axis_offset + gw->top;
       
   120 	GfxFillRect(x, y, right, y, GRAPH_AXIS_LINE_COLOUR);
       
   121 
       
   122 	/* Find the largest value that will be drawn. */
       
   123 	if (gw->num_on_x_axis == 0)
       
   124 		return;
       
   125 
       
   126 	assert(gw->num_on_x_axis > 0);
       
   127 	assert(gw->num_dataset > 0);
       
   128 
       
   129 	/* Start of with a value of twice the height of the graph in pixels. It's a
       
   130 	 * bit arbitrary, but it makes the cargo payment graph look a little nicer,
       
   131 	 * and prevents division by zero when calculating where the datapoint
       
   132 	 * should be drawn. */
       
   133 	highest_value = x_axis_offset * 2;
       
   134 
       
   135 	for (int i = 0; i < gw->num_dataset; i++) {
       
   136 		if (!HasBit(gw->excluded_data, i)) {
       
   137 			for (int j = 0; j < gw->num_on_x_axis; j++) {
       
   138 				OverflowSafeInt64 datapoint = gw->cost[i][j];
       
   139 
       
   140 				if (datapoint != INVALID_DATAPOINT) {
       
   141 					/* For now, if the graph has negative values the scaling is
       
   142 					 * symmetrical about the x axis, so take the absolute value
       
   143 					 * of each data point. */
       
   144 					highest_value = max(highest_value, abs(datapoint));
       
   145 				}
       
   146 			}
       
   147 		}
       
   148 	}
       
   149 
       
   150 	/* Round up highest_value so that it will divide cleanly into the number of
       
   151 	 * axis labels used. */
       
   152 	int round_val = highest_value % (GRAPH_NUM_LINES_Y - 1);
       
   153 	if (round_val != 0) highest_value += (GRAPH_NUM_LINES_Y - 1 - round_val);
       
   154 
       
   155 	/* draw text strings on the y axis */
       
   156 	int64 y_label = highest_value;
       
   157 	int64 y_label_separation = highest_value / (GRAPH_NUM_LINES_Y - 1);
       
   158 
       
   159 	/* If there are negative values, the graph goes from highest_value to
       
   160 	 * -highest_value, not highest_value to 0. */
       
   161 	if (gw->has_negative_values) y_label_separation *= 2;
       
   162 
       
   163 	x = gw->left + GRAPH_X_POSITION_BEGINNING + 1;
       
   164 	y = gw->top - 3;
       
   165 
       
   166 	for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
       
   167 		SetDParam(0, gw->format_str_y_axis);
       
   168 		SetDParam(1, y_label);
       
   169 		DrawStringRightAligned(x, y, STR_0170, GRAPH_AXIS_LABEL_COLOUR);
       
   170 
       
   171 		y_label -= y_label_separation;
       
   172 		y += (gw->height / (GRAPH_NUM_LINES_Y - 1));
       
   173 	}
       
   174 
       
   175 	/* draw strings on the x axis */
       
   176 	if (gw->month != 0xFF) {
       
   177 		x = gw->left + GRAPH_X_POSITION_BEGINNING;
       
   178 		y = gw->top + gw->height + 1;
       
   179 		byte month = gw->month;
       
   180 		Year year  = gw->year;
       
   181 		for (int i = 0; i < gw->num_on_x_axis; i++) {
       
   182 			SetDParam(0, month + STR_0162_JAN);
       
   183 			SetDParam(1, month + STR_0162_JAN + 2);
       
   184 			SetDParam(2, year);
       
   185 			DrawString(x, y, month == 0 ? STR_016F : STR_016E, GRAPH_AXIS_LABEL_COLOUR);
       
   186 
       
   187 			month += 3;
       
   188 			if (month >= 12) {
       
   189 				month = 0;
       
   190 				year++;
       
   191 			}
       
   192 			x += GRAPH_X_POSITION_SEPARATION;
       
   193 		}
       
   194 	} else {
       
   195 		/* Draw the label under the data point rather than on the grid line. */
       
   196 		x = gw->left + GRAPH_X_POSITION_BEGINNING + (GRAPH_X_POSITION_SEPARATION / 2) + 1;
       
   197 		y = gw->top + gw->height + 1;
       
   198 		uint16 label = gw->x_values_start;
       
   199 
       
   200 		for (int i = 0; i < gw->num_on_x_axis; i++) {
       
   201 			SetDParam(0, label);
       
   202 			DrawStringCentered(x, y, STR_01CB, GRAPH_AXIS_LABEL_COLOUR);
       
   203 
       
   204 			label += gw->x_values_increment;
       
   205 			x += GRAPH_X_POSITION_SEPARATION;
       
   206 		}
       
   207 	}
       
   208 
       
   209 	/* draw lines and dots */
       
   210 	for (int i = 0; i < gw->num_dataset; i++) {
       
   211 		if (!HasBit(gw->excluded_data, i)) {
       
   212 			/* Centre the dot between the grid lines. */
       
   213 			x = gw->left + GRAPH_X_POSITION_BEGINNING + (GRAPH_X_POSITION_SEPARATION / 2);
       
   214 
       
   215 			byte color  = gw->colors[i];
       
   216 			uint prev_x = INVALID_DATAPOINT_POS;
       
   217 			uint prev_y = INVALID_DATAPOINT_POS;
       
   218 
       
   219 			for (int j = 0; j < gw->num_on_x_axis; j++) {
       
   220 				OverflowSafeInt64 datapoint = gw->cost[i][j];
       
   221 
       
   222 				if (datapoint != INVALID_DATAPOINT) {
       
   223 					/*
       
   224 					 * Check whether we need to reduce the 'accuracy' of the
       
   225 					 * datapoint value and the highest value to splut overflows.
       
   226 					 * And when 'drawing' 'one million' or 'one million and one'
       
   227 					 * there is no significant difference, so the least
       
   228 					 * significant bits can just be removed.
       
   229 					 *
       
   230 					 * If there are more bits needed than would fit in a 32 bits
       
   231 					 * integer, so at about 31 bits because of the sign bit, the
       
   232 					 * least significant bits are removed.
       
   233 					 */
       
   234 					int mult_range = FindLastBit(x_axis_offset) + FindLastBit(abs(datapoint));
       
   235 					int reduce_range = max(mult_range - 31, 0);
       
   236 
       
   237 					/* Handle negative values differently (don't shift sign) */
       
   238 					if (datapoint < 0) {
       
   239 						datapoint = -(abs(datapoint) >> reduce_range);
       
   240 					} else {
       
   241 						datapoint >>= reduce_range;
       
   242 					}
       
   243 
       
   244 					y = gw->top + x_axis_offset - (x_axis_offset * datapoint) / (highest_value >> reduce_range);
       
   245 
       
   246 					/* Draw the point. */
       
   247 					GfxFillRect(x - 1, y - 1, x + 1, y + 1, color);
       
   248 
       
   249 					/* Draw the line connected to the previous point. */
       
   250 					if (prev_x != INVALID_DATAPOINT_POS) GfxDrawLine(prev_x, prev_y, x, y, color);
       
   251 
       
   252 					prev_x = x;
       
   253 					prev_y = y;
       
   254 				} else {
       
   255 					prev_x = INVALID_DATAPOINT_POS;
       
   256 					prev_y = INVALID_DATAPOINT_POS;
       
   257 				}
       
   258 
       
   259 				x += GRAPH_X_POSITION_SEPARATION;
       
   260 			}
       
   261 		}
       
   262 	}
       
   263 }
       
   264 
       
   265 /****************/
    31 /****************/
   266 /* GRAPH LEGEND */
    32 /* GRAPH LEGEND */
   267 /****************/
    33 /****************/
   268 
    34 
   269 static void GraphLegendWndProc(Window *w, WindowEvent *e)
    35 struct GraphLegendWindow : Window {
   270 {
    36 	GraphLegendWindow(const WindowDesc *desc, WindowNumber window_number) : Window(desc, window_number)
   271 	switch (e->event) {
    37 	{
   272 		case WE_CREATE:
    38 		for (uint i = 3; i < this->widget_count; i++) {
   273 			for (uint i = 3; i < w->widget_count; i++) {
    39 			if (!HasBit(_legend_excluded_players, i - 3)) this->LowerWidget(i);
   274 				if (!HasBit(_legend_excluded_players, i - 3)) w->LowerWidget(i);
    40 		}
   275 			}
    41 
   276 			break;
    42 		this->FindWindowPlacementAndResize(desc);
   277 
    43 	}
   278 		case WE_PAINT: {
    44 
   279 			const Player *p;
    45 	virtual void OnPaint()
   280 
    46 	{
   281 			FOR_ALL_PLAYERS(p) {
    47 		const Player *p;
   282 				if (p->is_active) continue;
    48 
   283 
    49 		FOR_ALL_PLAYERS(p) {
   284 				SetBit(_legend_excluded_players, p->index);
    50 			if (p->is_active) continue;
   285 				w->RaiseWidget(p->index + 3);
    51 
   286 			}
    52 			SetBit(_legend_excluded_players, p->index);
   287 
    53 			this->RaiseWidget(p->index + 3);
   288 			DrawWindowWidgets(w);
    54 		}
   289 
    55 
   290 			FOR_ALL_PLAYERS(p) {
    56 		this->DrawWidgets();
   291 				if (!p->is_active) continue;
    57 
   292 
    58 		FOR_ALL_PLAYERS(p) {
   293 				DrawPlayerIcon(p->index, 4, 18 + p->index * 12);
    59 			if (!p->is_active) continue;
   294 
    60 
   295 				SetDParam(0, p->index);
    61 			DrawPlayerIcon(p->index, 4, 18 + p->index * 12);
   296 				SetDParam(1, p->index);
    62 
   297 				DrawString(21, 17 + p->index * 12, STR_7021, HasBit(_legend_excluded_players, p->index) ? TC_BLACK : TC_WHITE);
    63 			SetDParam(0, p->index);
   298 			}
    64 			SetDParam(1, p->index);
   299 			break;
    65 			DrawString(21, 17 + p->index * 12, STR_7021, HasBit(_legend_excluded_players, p->index) ? TC_BLACK : TC_WHITE);
   300 		}
    66 		}
   301 
    67 	}
   302 		case WE_CLICK:
    68 
   303 			if (!IsInsideMM(e->we.click.widget, 3, 11)) return;
    69 	virtual void OnClick(Point pt, int widget)
   304 
    70 	{
   305 			ToggleBit(_legend_excluded_players, e->we.click.widget - 3);
    71 		if (!IsInsideMM(widget, 3, 11)) return;
   306 			w->ToggleWidgetLoweredState(e->we.click.widget);
    72 
   307 			w->SetDirty();
    73 		ToggleBit(_legend_excluded_players, widget - 3);
   308 			InvalidateWindow(WC_INCOME_GRAPH, 0);
    74 		this->ToggleWidgetLoweredState(widget);
   309 			InvalidateWindow(WC_OPERATING_PROFIT, 0);
    75 		this->SetDirty();
   310 			InvalidateWindow(WC_DELIVERED_CARGO, 0);
    76 		InvalidateWindow(WC_INCOME_GRAPH, 0);
   311 			InvalidateWindow(WC_PERFORMANCE_HISTORY, 0);
    77 		InvalidateWindow(WC_OPERATING_PROFIT, 0);
   312 			InvalidateWindow(WC_COMPANY_VALUE, 0);
    78 		InvalidateWindow(WC_DELIVERED_CARGO, 0);
   313 			break;
    79 		InvalidateWindow(WC_PERFORMANCE_HISTORY, 0);
   314 	}
    80 		InvalidateWindow(WC_COMPANY_VALUE, 0);
   315 }
    81 	}
       
    82 };
   316 
    83 
   317 static const Widget _graph_legend_widgets[] = {
    84 static const Widget _graph_legend_widgets[] = {
   318 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                       STR_018B_CLOSE_WINDOW},
    85 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                       STR_018B_CLOSE_WINDOW},
   319 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   249,     0,    13, STR_704E_KEY_TO_COMPANY_GRAPHS, STR_018C_WINDOW_TITLE_DRAG_THIS},
    86 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   249,     0,    13, STR_704E_KEY_TO_COMPANY_GRAPHS, STR_018C_WINDOW_TITLE_DRAG_THIS},
   320 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   249,    14,   113, 0x0,                            STR_NULL},
    87 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   249,    14,   113, 0x0,                            STR_NULL},
   332 static const WindowDesc _graph_legend_desc = {
    99 static const WindowDesc _graph_legend_desc = {
   333 	WDP_AUTO, WDP_AUTO, 250, 114, 250, 114,
   100 	WDP_AUTO, WDP_AUTO, 250, 114, 250, 114,
   334 	WC_GRAPH_LEGEND, WC_NONE,
   101 	WC_GRAPH_LEGEND, WC_NONE,
   335 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
   102 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
   336 	_graph_legend_widgets,
   103 	_graph_legend_widgets,
   337 	GraphLegendWndProc
       
   338 };
   104 };
   339 
   105 
   340 static void ShowGraphLegend()
   106 static void ShowGraphLegend()
   341 {
   107 {
   342 	AllocateWindowDescFront<Window>(&_graph_legend_desc, 0);
   108 	AllocateWindowDescFront<GraphLegendWindow>(&_graph_legend_desc, 0);
   343 }
   109 }
       
   110 
       
   111 /******************/
       
   112 /* BASE OF GRAPHS */
       
   113 /*****************/
       
   114 
       
   115 struct BaseGraphWindow : Window {
       
   116 protected:
       
   117 	enum {
       
   118 		GRAPH_MAX_DATASETS = 32,
       
   119 		GRAPH_AXIS_LABEL_COLOUR = TC_BLACK,
       
   120 		GRAPH_AXIS_LINE_COLOUR  = 215,
       
   121 
       
   122 		GRAPH_X_POSITION_BEGINNING  = 44,  ///< Start the graph 44 pixels from gd_left
       
   123 		GRAPH_X_POSITION_SEPARATION = 22,  ///< There are 22 pixels between each X value
       
   124 
       
   125 		GRAPH_NUM_LINES_Y = 9, ///< How many horizontal lines to draw.
       
   126 		/* 9 is convenient as that means the distance between them is the gd_height of the graph / 8,
       
   127 		* which is the same
       
   128 		* as height >> 3. */
       
   129 	};
       
   130 
       
   131 	uint excluded_data; ///< bitmask of the datasets that shouldn't be displayed.
       
   132 	byte num_dataset;
       
   133 	byte num_on_x_axis;
       
   134 	bool has_negative_values;
       
   135 	byte num_vert_lines;
       
   136 
       
   137 	/* The starting month and year that values are plotted against. If month is
       
   138 	 * 0xFF, use x_values_start and x_values_increment below instead. */
       
   139 	byte month;
       
   140 	Year year;
       
   141 
       
   142 	/* These values are used if the graph is being plotted against values
       
   143 	 * rather than the dates specified by month and year. */
       
   144 	uint16 x_values_start;
       
   145 	uint16 x_values_increment;
       
   146 
       
   147 	int gd_left, gd_top;  ///< Where to start drawing the graph, in pixels.
       
   148 	uint gd_height;    ///< The height of the graph in pixels.
       
   149 	StringID format_str_y_axis;
       
   150 	byte colors[GRAPH_MAX_DATASETS];
       
   151 	OverflowSafeInt64 cost[GRAPH_MAX_DATASETS][24]; ///< last 2 years
       
   152 
       
   153 	void DrawGraph() const
       
   154 	{
       
   155 		uint x, y;                       ///< Reused whenever x and y coordinates are needed.
       
   156 		OverflowSafeInt64 highest_value; ///< Highest value to be drawn.
       
   157 		int x_axis_offset;               ///< Distance from the top of the graph to the x axis.
       
   158 
       
   159 		/* the colors and cost array of GraphDrawer must accomodate
       
   160 		* both values for cargo and players. So if any are higher, quit */
       
   161 		assert(GRAPH_MAX_DATASETS >= (int)NUM_CARGO && GRAPH_MAX_DATASETS >= (int)MAX_PLAYERS);
       
   162 		assert(this->num_vert_lines > 0);
       
   163 
       
   164 		byte grid_colour = _colour_gradient[14][4];
       
   165 
       
   166 		/* The coordinates of the opposite edges of the graph. */
       
   167 		int bottom = this->gd_top + this->gd_height - 1;
       
   168 		int right  = this->gd_left + GRAPH_X_POSITION_BEGINNING + this->num_vert_lines * GRAPH_X_POSITION_SEPARATION - 1;
       
   169 
       
   170 		/* Draw the vertical grid lines. */
       
   171 
       
   172 		/* Don't draw the first line, as that's where the axis will be. */
       
   173 		x = this->gd_left + GRAPH_X_POSITION_BEGINNING + GRAPH_X_POSITION_SEPARATION;
       
   174 
       
   175 		for (int i = 0; i < this->num_vert_lines; i++) {
       
   176 			GfxFillRect(x, this->gd_top, x, bottom, grid_colour);
       
   177 			x += GRAPH_X_POSITION_SEPARATION;
       
   178 		}
       
   179 
       
   180 		/* Draw the horizontal grid lines. */
       
   181 		x = this->gd_left + GRAPH_X_POSITION_BEGINNING;
       
   182 		y = this->gd_height + this->gd_top;
       
   183 
       
   184 		for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
       
   185 			GfxFillRect(x, y, right, y, grid_colour);
       
   186 			y -= (this->gd_height / (GRAPH_NUM_LINES_Y - 1));
       
   187 		}
       
   188 
       
   189 		/* Draw the y axis. */
       
   190 		GfxFillRect(x, this->gd_top, x, bottom, GRAPH_AXIS_LINE_COLOUR);
       
   191 
       
   192 		/* Find the distance from the gd_top of the graph to the x axis. */
       
   193 		x_axis_offset = this->gd_height;
       
   194 
       
   195 		/* The graph is currently symmetrical about the x axis. */
       
   196 		if (this->has_negative_values) x_axis_offset /= 2;
       
   197 
       
   198 		/* Draw the x axis. */
       
   199 		y = x_axis_offset + this->gd_top;
       
   200 		GfxFillRect(x, y, right, y, GRAPH_AXIS_LINE_COLOUR);
       
   201 
       
   202 		/* Find the largest value that will be drawn. */
       
   203 		if (this->num_on_x_axis == 0)
       
   204 			return;
       
   205 
       
   206 		assert(this->num_on_x_axis > 0);
       
   207 		assert(this->num_dataset > 0);
       
   208 
       
   209 		/* Start of with a value of twice the gd_height of the graph in pixels. It's a
       
   210 		* bit arbitrary, but it makes the cargo payment graph look a little nicer,
       
   211 		* and prevents division by zero when calculating where the datapoint
       
   212 		* should be drawn. */
       
   213 		highest_value = x_axis_offset * 2;
       
   214 
       
   215 		for (int i = 0; i < this->num_dataset; i++) {
       
   216 			if (!HasBit(this->excluded_data, i)) {
       
   217 				for (int j = 0; j < this->num_on_x_axis; j++) {
       
   218 					OverflowSafeInt64 datapoint = this->cost[i][j];
       
   219 
       
   220 					if (datapoint != INVALID_DATAPOINT) {
       
   221 						/* For now, if the graph has negative values the scaling is
       
   222 						* symmetrical about the x axis, so take the absolute value
       
   223 						* of each data point. */
       
   224 						highest_value = max(highest_value, abs(datapoint));
       
   225 					}
       
   226 				}
       
   227 			}
       
   228 		}
       
   229 
       
   230 		/* Round up highest_value so that it will divide cleanly into the number of
       
   231 		* axis labels used. */
       
   232 		int round_val = highest_value % (GRAPH_NUM_LINES_Y - 1);
       
   233 		if (round_val != 0) highest_value += (GRAPH_NUM_LINES_Y - 1 - round_val);
       
   234 
       
   235 		/* draw text strings on the y axis */
       
   236 		int64 y_label = highest_value;
       
   237 		int64 y_label_separation = highest_value / (GRAPH_NUM_LINES_Y - 1);
       
   238 
       
   239 		/* If there are negative values, the graph goes from highest_value to
       
   240 		* -highest_value, not highest_value to 0. */
       
   241 		if (this->has_negative_values) y_label_separation *= 2;
       
   242 
       
   243 		x = this->gd_left + GRAPH_X_POSITION_BEGINNING + 1;
       
   244 		y = this->gd_top - 3;
       
   245 
       
   246 		for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
       
   247 			SetDParam(0, this->format_str_y_axis);
       
   248 			SetDParam(1, y_label);
       
   249 			DrawStringRightAligned(x, y, STR_0170, GRAPH_AXIS_LABEL_COLOUR);
       
   250 
       
   251 			y_label -= y_label_separation;
       
   252 			y += (this->gd_height / (GRAPH_NUM_LINES_Y - 1));
       
   253 		}
       
   254 
       
   255 		/* draw strings on the x axis */
       
   256 		if (this->month != 0xFF) {
       
   257 			x = this->gd_left + GRAPH_X_POSITION_BEGINNING;
       
   258 			y = this->gd_top + this->gd_height + 1;
       
   259 			byte month = this->month;
       
   260 			Year year  = this->year;
       
   261 			for (int i = 0; i < this->num_on_x_axis; i++) {
       
   262 				SetDParam(0, month + STR_0162_JAN);
       
   263 				SetDParam(1, month + STR_0162_JAN + 2);
       
   264 				SetDParam(2, year);
       
   265 				DrawString(x, y, month == 0 ? STR_016F : STR_016E, GRAPH_AXIS_LABEL_COLOUR);
       
   266 
       
   267 				month += 3;
       
   268 				if (month >= 12) {
       
   269 					month = 0;
       
   270 					year++;
       
   271 				}
       
   272 				x += GRAPH_X_POSITION_SEPARATION;
       
   273 			}
       
   274 		} else {
       
   275 			/* Draw the label under the data point rather than on the grid line. */
       
   276 			x = this->gd_left + GRAPH_X_POSITION_BEGINNING + (GRAPH_X_POSITION_SEPARATION / 2) + 1;
       
   277 			y = this->gd_top + this->gd_height + 1;
       
   278 			uint16 label = this->x_values_start;
       
   279 
       
   280 			for (int i = 0; i < this->num_on_x_axis; i++) {
       
   281 				SetDParam(0, label);
       
   282 				DrawStringCentered(x, y, STR_01CB, GRAPH_AXIS_LABEL_COLOUR);
       
   283 
       
   284 				label += this->x_values_increment;
       
   285 				x += GRAPH_X_POSITION_SEPARATION;
       
   286 			}
       
   287 		}
       
   288 
       
   289 		/* draw lines and dots */
       
   290 		for (int i = 0; i < this->num_dataset; i++) {
       
   291 			if (!HasBit(this->excluded_data, i)) {
       
   292 				/* Centre the dot between the grid lines. */
       
   293 				x = this->gd_left + GRAPH_X_POSITION_BEGINNING + (GRAPH_X_POSITION_SEPARATION / 2);
       
   294 
       
   295 				byte color  = this->colors[i];
       
   296 				uint prev_x = INVALID_DATAPOINT_POS;
       
   297 				uint prev_y = INVALID_DATAPOINT_POS;
       
   298 
       
   299 				for (int j = 0; j < this->num_on_x_axis; j++) {
       
   300 					OverflowSafeInt64 datapoint = this->cost[i][j];
       
   301 
       
   302 					if (datapoint != INVALID_DATAPOINT) {
       
   303 						/*
       
   304 						* Check whether we need to reduce the 'accuracy' of the
       
   305 						* datapoint value and the highest value to splut overflows.
       
   306 						* And when 'drawing' 'one million' or 'one million and one'
       
   307 						* there is no significant difference, so the least
       
   308 						* significant bits can just be removed.
       
   309 						*
       
   310 						* If there are more bits needed than would fit in a 32 bits
       
   311 						* integer, so at about 31 bits because of the sign bit, the
       
   312 						* least significant bits are removed.
       
   313 						*/
       
   314 						int mult_range = FindLastBit(x_axis_offset) + FindLastBit(abs(datapoint));
       
   315 						int reduce_range = max(mult_range - 31, 0);
       
   316 
       
   317 						/* Handle negative values differently (don't shift sign) */
       
   318 						if (datapoint < 0) {
       
   319 							datapoint = -(abs(datapoint) >> reduce_range);
       
   320 						} else {
       
   321 							datapoint >>= reduce_range;
       
   322 						}
       
   323 
       
   324 						y = this->gd_top + x_axis_offset - (x_axis_offset * datapoint) / (highest_value >> reduce_range);
       
   325 
       
   326 						/* Draw the point. */
       
   327 						GfxFillRect(x - 1, y - 1, x + 1, y + 1, color);
       
   328 
       
   329 						/* Draw the line connected to the previous point. */
       
   330 						if (prev_x != INVALID_DATAPOINT_POS) GfxDrawLine(prev_x, prev_y, x, y, color);
       
   331 
       
   332 						prev_x = x;
       
   333 						prev_y = y;
       
   334 					} else {
       
   335 						prev_x = INVALID_DATAPOINT_POS;
       
   336 						prev_y = INVALID_DATAPOINT_POS;
       
   337 					}
       
   338 
       
   339 					x += GRAPH_X_POSITION_SEPARATION;
       
   340 				}
       
   341 			}
       
   342 		}
       
   343 	}
       
   344 
       
   345 
       
   346 	BaseGraphWindow(const WindowDesc *desc, WindowNumber window_number, int left,
       
   347 									int top, int height, bool has_negative_values, StringID format_str_y_axis) :
       
   348 			Window(desc, window_number), has_negative_values(has_negative_values),
       
   349 			gd_left(left), gd_top(top), gd_height(height), format_str_y_axis(format_str_y_axis)
       
   350 	{
       
   351 		InvalidateWindow(WC_GRAPH_LEGEND, 0);
       
   352 	}
       
   353 
       
   354 public:
       
   355 	virtual void OnPaint()
       
   356 	{
       
   357 		this->DrawWidgets();
       
   358 
       
   359 		uint excluded_players = _legend_excluded_players;
       
   360 
       
   361 		/* Exclude the players which aren't valid */
       
   362 		const Player* p;
       
   363 		FOR_ALL_PLAYERS(p) {
       
   364 			if (!p->is_active) SetBit(excluded_players, p->index);
       
   365 		}
       
   366 		this->excluded_data = excluded_players;
       
   367 		this->num_vert_lines = 24;
       
   368 
       
   369 		byte nums = 0;
       
   370 		FOR_ALL_PLAYERS(p) {
       
   371 			if (p->is_active) nums = max(nums, p->num_valid_stat_ent);
       
   372 		}
       
   373 		this->num_on_x_axis = min(nums, 24);
       
   374 
       
   375 		int mo = (_cur_month / 3 - nums) * 3;
       
   376 		int yr = _cur_year;
       
   377 		while (mo < 0) {
       
   378 			yr--;
       
   379 			mo += 12;
       
   380 		}
       
   381 
       
   382 		this->year = yr;
       
   383 		this->month = mo;
       
   384 
       
   385 		int numd = 0;
       
   386 		FOR_ALL_PLAYERS(p) {
       
   387 			if (p->is_active) {
       
   388 				this->colors[numd] = _colour_gradient[p->player_color][6];
       
   389 				for (int j = this->num_on_x_axis, i = 0; --j >= 0;) {
       
   390 					this->cost[numd][i] = (j >= p->num_valid_stat_ent) ? INVALID_DATAPOINT : GetGraphData(p, j);
       
   391 					i++;
       
   392 				}
       
   393 			}
       
   394 			numd++;
       
   395 		}
       
   396 
       
   397 		this->num_dataset = numd;
       
   398 
       
   399 		this->DrawGraph();
       
   400 	}
       
   401 
       
   402 	virtual OverflowSafeInt64 GetGraphData(const Player *p, int j)
       
   403 	{
       
   404 		return INVALID_DATAPOINT;
       
   405 	}
       
   406 
       
   407 	virtual void OnClick(Point pt, int widget)
       
   408 	{
       
   409 		/* Clicked on legend? */
       
   410 		if (widget == 2) ShowGraphLegend();
       
   411 	}
       
   412 };
   344 
   413 
   345 /********************/
   414 /********************/
   346 /* OPERATING PROFIT */
   415 /* OPERATING PROFIT */
   347 /********************/
   416 /********************/
   348 
   417 
   349 static void SetupGraphDrawerForPlayers(GraphDrawer *gd)
   418 struct OperatingProfitGraphWindow : BaseGraphWindow {
   350 {
   419 	OperatingProfitGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
   351 	const Player* p;
   420 			BaseGraphWindow(desc, window_number, 2, 18, 136, true, STR_CURRCOMPACT)
   352 	uint excluded_players = _legend_excluded_players;
   421 	{
   353 	byte nums;
   422 		this->FindWindowPlacementAndResize(desc);
   354 	int mo, yr;
   423 	}
   355 
   424 
   356 	/* Exclude the players which aren't valid */
   425 	virtual OverflowSafeInt64 GetGraphData(const Player *p, int j)
   357 	FOR_ALL_PLAYERS(p) {
   426 	{
   358 		if (!p->is_active) SetBit(excluded_players, p->index);
   427 		return p->old_economy[j].income + p->old_economy[j].expenses;
   359 	}
   428 	}
   360 	gd->excluded_data = excluded_players;
   429 };
   361 	gd->num_vert_lines = 24;
       
   362 
       
   363 	nums = 0;
       
   364 	FOR_ALL_PLAYERS(p) {
       
   365 		if (p->is_active) nums = max(nums, p->num_valid_stat_ent);
       
   366 	}
       
   367 	gd->num_on_x_axis = min(nums, 24);
       
   368 
       
   369 	mo = (_cur_month / 3 - nums) * 3;
       
   370 	yr = _cur_year;
       
   371 	while (mo < 0) {
       
   372 		yr--;
       
   373 		mo += 12;
       
   374 	}
       
   375 
       
   376 	gd->year = yr;
       
   377 	gd->month = mo;
       
   378 }
       
   379 
       
   380 static void OperatingProfitWndProc(Window *w, WindowEvent *e)
       
   381 {
       
   382 	switch (e->event) {
       
   383 		case WE_PAINT: {
       
   384 			GraphDrawer gd;
       
   385 			const Player* p;
       
   386 
       
   387 			DrawWindowWidgets(w);
       
   388 
       
   389 			gd.left = 2;
       
   390 			gd.top = 18;
       
   391 			gd.height = 136;
       
   392 			gd.has_negative_values = true;
       
   393 			gd.format_str_y_axis = STR_CURRCOMPACT;
       
   394 
       
   395 			SetupGraphDrawerForPlayers(&gd);
       
   396 
       
   397 			int numd = 0;
       
   398 			FOR_ALL_PLAYERS(p) {
       
   399 				if (p->is_active) {
       
   400 					gd.colors[numd] = _colour_gradient[p->player_color][6];
       
   401 					for (int j = gd.num_on_x_axis, i = 0; --j >= 0;) {
       
   402 						gd.cost[numd][i] = (j >= p->num_valid_stat_ent) ? INVALID_DATAPOINT : (p->old_economy[j].income + p->old_economy[j].expenses);
       
   403 						i++;
       
   404 					}
       
   405 				}
       
   406 				numd++;
       
   407 			}
       
   408 
       
   409 			gd.num_dataset = numd;
       
   410 
       
   411 			DrawGraph(&gd);
       
   412 			break;
       
   413 		}
       
   414 
       
   415 		case WE_CLICK:
       
   416 			/* Clicked on legend? */
       
   417 			if (e->we.click.widget == 2) ShowGraphLegend();
       
   418 			break;
       
   419 	}
       
   420 }
       
   421 
   430 
   422 static const Widget _operating_profit_widgets[] = {
   431 static const Widget _operating_profit_widgets[] = {
   423 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                        STR_018B_CLOSE_WINDOW},
   432 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                        STR_018B_CLOSE_WINDOW},
   424 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7025_OPERATING_PROFIT_GRAPH, STR_018C_WINDOW_TITLE_DRAG_THIS},
   433 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7025_OPERATING_PROFIT_GRAPH, STR_018C_WINDOW_TITLE_DRAG_THIS},
   425 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,                    STR_704D_SHOW_KEY_TO_GRAPHS},
   434 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,                    STR_704D_SHOW_KEY_TO_GRAPHS},
   430 static const WindowDesc _operating_profit_desc = {
   439 static const WindowDesc _operating_profit_desc = {
   431 	WDP_AUTO, WDP_AUTO, 576, 174, 576, 174,
   440 	WDP_AUTO, WDP_AUTO, 576, 174, 576, 174,
   432 	WC_OPERATING_PROFIT, WC_NONE,
   441 	WC_OPERATING_PROFIT, WC_NONE,
   433 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   442 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   434 	_operating_profit_widgets,
   443 	_operating_profit_widgets,
   435 	OperatingProfitWndProc
       
   436 };
   444 };
   437 
   445 
   438 
   446 
   439 void ShowOperatingProfitGraph()
   447 void ShowOperatingProfitGraph()
   440 {
   448 {
   441 	if (AllocateWindowDescFront<Window>(&_operating_profit_desc, 0)) {
   449 	AllocateWindowDescFront<OperatingProfitGraphWindow>(&_operating_profit_desc, 0);
   442 		InvalidateWindow(WC_GRAPH_LEGEND, 0);
       
   443 	}
       
   444 }
   450 }
   445 
   451 
   446 
   452 
   447 /****************/
   453 /****************/
   448 /* INCOME GRAPH */
   454 /* INCOME GRAPH */
   449 /****************/
   455 /****************/
   450 
   456 
   451 static void IncomeGraphWndProc(Window *w, WindowEvent *e)
   457 struct IncomeGraphWindow : BaseGraphWindow {
   452 {
   458 	IncomeGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
   453 	switch (e->event) {
   459 			BaseGraphWindow(desc, window_number, 2, 18, 104, false, STR_CURRCOMPACT)
   454 		case WE_PAINT: {
   460 	{
   455 			GraphDrawer gd;
   461 		this->FindWindowPlacementAndResize(desc);
   456 			const Player* p;
   462 	}
   457 
   463 
   458 			DrawWindowWidgets(w);
   464 	virtual OverflowSafeInt64 GetGraphData(const Player *p, int j)
   459 
   465 	{
   460 			gd.left = 2;
   466 		return p->old_economy[j].income;
   461 			gd.top = 18;
   467 	}
   462 			gd.height = 104;
   468 };
   463 			gd.has_negative_values = false;
       
   464 			gd.format_str_y_axis = STR_CURRCOMPACT;
       
   465 			SetupGraphDrawerForPlayers(&gd);
       
   466 
       
   467 			int numd = 0;
       
   468 			FOR_ALL_PLAYERS(p) {
       
   469 				if (p->is_active) {
       
   470 					gd.colors[numd] = _colour_gradient[p->player_color][6];
       
   471 					for (int j = gd.num_on_x_axis, i = 0; --j >= 0;) {
       
   472 						gd.cost[numd][i] = (j >= p->num_valid_stat_ent) ? INVALID_DATAPOINT : p->old_economy[j].income;
       
   473 						i++;
       
   474 					}
       
   475 				}
       
   476 				numd++;
       
   477 			}
       
   478 
       
   479 			gd.num_dataset = numd;
       
   480 
       
   481 			DrawGraph(&gd);
       
   482 			break;
       
   483 		}
       
   484 
       
   485 		case WE_CLICK:
       
   486 			if (e->we.click.widget == 2) ShowGraphLegend();
       
   487 			break;
       
   488 	}
       
   489 }
       
   490 
   469 
   491 static const Widget _income_graph_widgets[] = {
   470 static const Widget _income_graph_widgets[] = {
   492 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,              STR_018B_CLOSE_WINDOW},
   471 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,              STR_018B_CLOSE_WINDOW},
   493 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7022_INCOME_GRAPH, STR_018C_WINDOW_TITLE_DRAG_THIS},
   472 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7022_INCOME_GRAPH, STR_018C_WINDOW_TITLE_DRAG_THIS},
   494 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,          STR_704D_SHOW_KEY_TO_GRAPHS},
   473 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,          STR_704D_SHOW_KEY_TO_GRAPHS},
   499 static const WindowDesc _income_graph_desc = {
   478 static const WindowDesc _income_graph_desc = {
   500 	WDP_AUTO, WDP_AUTO, 576, 142, 576, 142,
   479 	WDP_AUTO, WDP_AUTO, 576, 142, 576, 142,
   501 	WC_INCOME_GRAPH, WC_NONE,
   480 	WC_INCOME_GRAPH, WC_NONE,
   502 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   481 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   503 	_income_graph_widgets,
   482 	_income_graph_widgets,
   504 	IncomeGraphWndProc
       
   505 };
   483 };
   506 
   484 
   507 void ShowIncomeGraph()
   485 void ShowIncomeGraph()
   508 {
   486 {
   509 	if (AllocateWindowDescFront<Window>(&_income_graph_desc, 0)) {
   487 	AllocateWindowDescFront<IncomeGraphWindow>(&_income_graph_desc, 0);
   510 		InvalidateWindow(WC_GRAPH_LEGEND, 0);
       
   511 	}
       
   512 }
   488 }
   513 
   489 
   514 /*******************/
   490 /*******************/
   515 /* DELIVERED CARGO */
   491 /* DELIVERED CARGO */
   516 /*******************/
   492 /*******************/
   517 
   493 
   518 static void DeliveredCargoGraphWndProc(Window *w, WindowEvent *e)
   494 struct DeliveredCargoGraphWindow : BaseGraphWindow {
   519 {
   495 	DeliveredCargoGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
   520 	switch (e->event) {
   496 			BaseGraphWindow(desc, window_number, 2, 18, 104, false, STR_7024)
   521 		case WE_PAINT: {
   497 	{
   522 			GraphDrawer gd;
   498 		this->FindWindowPlacementAndResize(desc);
   523 			const Player* p;
   499 	}
   524 
   500 
   525 			DrawWindowWidgets(w);
   501 	virtual OverflowSafeInt64 GetGraphData(const Player *p, int j)
   526 
   502 	{
   527 			gd.left = 2;
   503 		return p->old_economy[j].delivered_cargo;
   528 			gd.top = 18;
   504 	}
   529 			gd.height = 104;
   505 };
   530 			gd.has_negative_values = false;
       
   531 			gd.format_str_y_axis = STR_7024;
       
   532 			SetupGraphDrawerForPlayers(&gd);
       
   533 
       
   534 			int numd = 0;
       
   535 			FOR_ALL_PLAYERS(p) {
       
   536 				if (p->is_active) {
       
   537 					gd.colors[numd] = _colour_gradient[p->player_color][6];
       
   538 					for (int j = gd.num_on_x_axis, i = 0; --j >= 0;) {
       
   539 						gd.cost[numd][i] = (j >= p->num_valid_stat_ent) ? INVALID_DATAPOINT : (OverflowSafeInt64)p->old_economy[j].delivered_cargo;
       
   540 						i++;
       
   541 					}
       
   542 				}
       
   543 				numd++;
       
   544 			}
       
   545 
       
   546 			gd.num_dataset = numd;
       
   547 
       
   548 			DrawGraph(&gd);
       
   549 			break;
       
   550 		}
       
   551 
       
   552 		case WE_CLICK:
       
   553 			if (e->we.click.widget == 2) ShowGraphLegend();
       
   554 			break;
       
   555 	}
       
   556 }
       
   557 
   506 
   558 static const Widget _delivered_cargo_graph_widgets[] = {
   507 static const Widget _delivered_cargo_graph_widgets[] = {
   559 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                          STR_018B_CLOSE_WINDOW},
   508 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                          STR_018B_CLOSE_WINDOW},
   560 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7050_UNITS_OF_CARGO_DELIVERED, STR_018C_WINDOW_TITLE_DRAG_THIS},
   509 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7050_UNITS_OF_CARGO_DELIVERED, STR_018C_WINDOW_TITLE_DRAG_THIS},
   561 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,                      STR_704D_SHOW_KEY_TO_GRAPHS},
   510 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,                      STR_704D_SHOW_KEY_TO_GRAPHS},
   566 static const WindowDesc _delivered_cargo_graph_desc = {
   515 static const WindowDesc _delivered_cargo_graph_desc = {
   567 	WDP_AUTO, WDP_AUTO, 576, 142, 576, 142,
   516 	WDP_AUTO, WDP_AUTO, 576, 142, 576, 142,
   568 	WC_DELIVERED_CARGO, WC_NONE,
   517 	WC_DELIVERED_CARGO, WC_NONE,
   569 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   518 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   570 	_delivered_cargo_graph_widgets,
   519 	_delivered_cargo_graph_widgets,
   571 	DeliveredCargoGraphWndProc
       
   572 };
   520 };
   573 
   521 
   574 void ShowDeliveredCargoGraph()
   522 void ShowDeliveredCargoGraph()
   575 {
   523 {
   576 	if (AllocateWindowDescFront<Window>(&_delivered_cargo_graph_desc, 0)) {
   524 	AllocateWindowDescFront<DeliveredCargoGraphWindow>(&_delivered_cargo_graph_desc, 0);
   577 		InvalidateWindow(WC_GRAPH_LEGEND, 0);
       
   578 	}
       
   579 }
   525 }
   580 
   526 
   581 /***********************/
   527 /***********************/
   582 /* PERFORMANCE HISTORY */
   528 /* PERFORMANCE HISTORY */
   583 /***********************/
   529 /***********************/
   584 
   530 
   585 static void PerformanceHistoryWndProc(Window *w, WindowEvent *e)
   531 struct PerformanceHistoryGraphWindow : BaseGraphWindow {
   586 {
   532 	PerformanceHistoryGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
   587 	switch (e->event) {
   533 			BaseGraphWindow(desc, window_number, 2, 18, 200, false, STR_7024)
   588 		case WE_PAINT: {
   534 	{
   589 			GraphDrawer gd;
   535 		this->FindWindowPlacementAndResize(desc);
   590 			const Player* p;
   536 	}
   591 
   537 
   592 			DrawWindowWidgets(w);
   538 	virtual OverflowSafeInt64 GetGraphData(const Player *p, int j)
   593 
   539 	{
   594 			gd.left = 2;
   540 		return p->old_economy[j].performance_history;
   595 			gd.top = 18;
   541 	}
   596 			gd.height = 200;
   542 
   597 			gd.has_negative_values = false;
   543 	virtual void OnClick(Point pt, int widget)
   598 			gd.format_str_y_axis = STR_7024;
   544 	{
   599 			SetupGraphDrawerForPlayers(&gd);
   545 		if (widget == 3) ShowPerformanceRatingDetail();
   600 
   546 		this->BaseGraphWindow::OnClick(pt, widget);
   601 			int numd = 0;
   547 	}
   602 			FOR_ALL_PLAYERS(p) {
   548 };
   603 				if (p->is_active) {
       
   604 					gd.colors[numd] = _colour_gradient[p->player_color][6];
       
   605 					for (int j = gd.num_on_x_axis, i = 0; --j >= 0;) {
       
   606 						gd.cost[numd][i] = (j >= p->num_valid_stat_ent) ? INVALID_DATAPOINT : (OverflowSafeInt64)p->old_economy[j].performance_history;
       
   607 						i++;
       
   608 					}
       
   609 				}
       
   610 				numd++;
       
   611 			}
       
   612 
       
   613 			gd.num_dataset = numd;
       
   614 
       
   615 			DrawGraph(&gd);
       
   616 			break;
       
   617 		}
       
   618 
       
   619 		case WE_CLICK:
       
   620 			if (e->we.click.widget == 2) ShowGraphLegend();
       
   621 			if (e->we.click.widget == 3) ShowPerformanceRatingDetail();
       
   622 			break;
       
   623 	}
       
   624 }
       
   625 
   549 
   626 static const Widget _performance_history_widgets[] = {
   550 static const Widget _performance_history_widgets[] = {
   627 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                             STR_018B_CLOSE_WINDOW},
   551 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                             STR_018B_CLOSE_WINDOW},
   628 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   475,     0,    13, STR_7051_COMPANY_PERFORMANCE_RATINGS, STR_018C_WINDOW_TITLE_DRAG_THIS},
   552 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   475,     0,    13, STR_7051_COMPANY_PERFORMANCE_RATINGS, STR_018C_WINDOW_TITLE_DRAG_THIS},
   629 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,                         STR_704D_SHOW_KEY_TO_GRAPHS},
   553 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,                         STR_704D_SHOW_KEY_TO_GRAPHS},
   635 static const WindowDesc _performance_history_desc = {
   559 static const WindowDesc _performance_history_desc = {
   636 	WDP_AUTO, WDP_AUTO, 576, 238, 576, 238,
   560 	WDP_AUTO, WDP_AUTO, 576, 238, 576, 238,
   637 	WC_PERFORMANCE_HISTORY, WC_NONE,
   561 	WC_PERFORMANCE_HISTORY, WC_NONE,
   638 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   562 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   639 	_performance_history_widgets,
   563 	_performance_history_widgets,
   640 	PerformanceHistoryWndProc
       
   641 };
   564 };
   642 
   565 
   643 void ShowPerformanceHistoryGraph()
   566 void ShowPerformanceHistoryGraph()
   644 {
   567 {
   645 	if (AllocateWindowDescFront<Window>(&_performance_history_desc, 0)) {
   568 	AllocateWindowDescFront<PerformanceHistoryGraphWindow>(&_performance_history_desc, 0);
   646 		InvalidateWindow(WC_GRAPH_LEGEND, 0);
       
   647 	}
       
   648 }
   569 }
   649 
   570 
   650 /*****************/
   571 /*****************/
   651 /* COMPANY VALUE */
   572 /* COMPANY VALUE */
   652 /*****************/
   573 /*****************/
   653 
   574 
   654 static void CompanyValueGraphWndProc(Window *w, WindowEvent *e)
   575 struct CompanyValueGraphWindow : BaseGraphWindow {
   655 {
   576 	CompanyValueGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
   656 	switch (e->event) {
   577 			BaseGraphWindow(desc, window_number, 2, 18, 200, false, STR_CURRCOMPACT)
   657 		case WE_PAINT: {
   578 	{
   658 			GraphDrawer gd;
   579 		this->FindWindowPlacementAndResize(desc);
   659 			const Player* p;
   580 	}
   660 
   581 
   661 			DrawWindowWidgets(w);
   582 	virtual OverflowSafeInt64 GetGraphData(const Player *p, int j)
   662 
   583 	{
   663 			gd.left = 2;
   584 		return p->old_economy[j].company_value;
   664 			gd.top = 18;
   585 	}
   665 			gd.height = 200;
   586 };
   666 			gd.has_negative_values = false;
       
   667 			gd.format_str_y_axis = STR_CURRCOMPACT;
       
   668 			SetupGraphDrawerForPlayers(&gd);
       
   669 
       
   670 			int numd = 0;
       
   671 			FOR_ALL_PLAYERS(p) {
       
   672 				if (p->is_active) {
       
   673 					gd.colors[numd] = _colour_gradient[p->player_color][6];
       
   674 					for (int j = gd.num_on_x_axis, i = 0; --j >= 0;) {
       
   675 						gd.cost[numd][i] = (j >= p->num_valid_stat_ent) ? INVALID_DATAPOINT : p->old_economy[j].company_value;
       
   676 						i++;
       
   677 					}
       
   678 				}
       
   679 				numd++;
       
   680 			}
       
   681 
       
   682 			gd.num_dataset = numd;
       
   683 
       
   684 			DrawGraph(&gd);
       
   685 			break;
       
   686 		}
       
   687 
       
   688 		case WE_CLICK:
       
   689 			if (e->we.click.widget == 2) ShowGraphLegend();
       
   690 			break;
       
   691 	}
       
   692 }
       
   693 
   587 
   694 static const Widget _company_value_graph_widgets[] = {
   588 static const Widget _company_value_graph_widgets[] = {
   695 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                STR_018B_CLOSE_WINDOW},
   589 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                STR_018B_CLOSE_WINDOW},
   696 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7052_COMPANY_VALUES, STR_018C_WINDOW_TITLE_DRAG_THIS},
   590 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7052_COMPANY_VALUES, STR_018C_WINDOW_TITLE_DRAG_THIS},
   697 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,            STR_704D_SHOW_KEY_TO_GRAPHS},
   591 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,            STR_704D_SHOW_KEY_TO_GRAPHS},
   702 static const WindowDesc _company_value_graph_desc = {
   596 static const WindowDesc _company_value_graph_desc = {
   703 	WDP_AUTO, WDP_AUTO, 576, 238, 576, 238,
   597 	WDP_AUTO, WDP_AUTO, 576, 238, 576, 238,
   704 	WC_COMPANY_VALUE, WC_NONE,
   598 	WC_COMPANY_VALUE, WC_NONE,
   705 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   599 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   706 	_company_value_graph_widgets,
   600 	_company_value_graph_widgets,
   707 	CompanyValueGraphWndProc
       
   708 };
   601 };
   709 
   602 
   710 void ShowCompanyValueGraph()
   603 void ShowCompanyValueGraph()
   711 {
   604 {
   712 	if (AllocateWindowDescFront<Window>(&_company_value_graph_desc, 0)) {
   605 	AllocateWindowDescFront<CompanyValueGraphWindow>(&_company_value_graph_desc, 0);
   713 		InvalidateWindow(WC_GRAPH_LEGEND, 0);
       
   714 	}
       
   715 }
   606 }
   716 
   607 
   717 /*****************/
   608 /*****************/
   718 /* PAYMENT RATES */
   609 /* PAYMENT RATES */
   719 /*****************/
   610 /*****************/
   720 
   611 
   721 static void CargoPaymentRatesWndProc(Window *w, WindowEvent *e)
   612 struct PaymentRatesGraphWindow : BaseGraphWindow {
   722 {
   613 	PaymentRatesGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
   723 	switch (e->event) {
   614 			BaseGraphWindow(desc, window_number, 2, 24, 200, false, STR_CURRCOMPACT)
   724 		case WE_PAINT: {
   615 	{
   725 			GraphDrawer gd;
   616 		uint num_active = 0;
   726 
   617 		for (CargoID c = 0; c < NUM_CARGO; c++) {
   727 			DrawWindowWidgets(w);
   618 			if (GetCargo(c)->IsValid()) num_active++;
   728 
   619 		}
   729 			int x = 495;
   620 
   730 			int y = 24;
   621 		/* Resize the window to fit the cargo types */
   731 
   622 		ResizeWindow(this, 0, max(num_active, 12U) * 8);
   732 			gd.excluded_data = _legend_excluded_cargo;
   623 
   733 			gd.left = 2;
   624 		/* Add widgets for each cargo type */
   734 			gd.top = 24;
   625 		this->widget_count += num_active;
   735 			gd.height = w->height - 38;
   626 		this->widget = ReallocT(this->widget, this->widget_count + 1);
   736 			gd.has_negative_values = false;
   627 		this->widget[this->widget_count].type = WWT_LAST;
   737 			gd.format_str_y_axis = STR_CURRCOMPACT;
   628 
   738 			gd.num_on_x_axis = 20;
   629 		/* Set the properties of each widget */
   739 			gd.num_vert_lines = 20;
   630 		for (uint i = 0; i != num_active; i++) {
   740 			gd.month = 0xFF;
   631 			Widget *wi = &this->widget[3 + i];
   741 			gd.x_values_start     = 10;
   632 			wi->type     = WWT_PANEL;
   742 			gd.x_values_increment = 10;
   633 			wi->display_flags = RESIZE_NONE;
   743 
   634 			wi->color    = 12;
   744 			uint i = 0;
   635 			wi->left     = 493;
   745 			for (CargoID c = 0; c < NUM_CARGO; c++) {
   636 			wi->right    = 562;
   746 				const CargoSpec *cs = GetCargo(c);
   637 			wi->top      = 24 + i * 8;
   747 				if (!cs->IsValid()) continue;
   638 			wi->bottom   = wi->top + 7;
   748 
   639 			wi->data     = 0;
   749 				/* Only draw labels for widgets that exist. If the widget doesn't
   640 			wi->tooltips = STR_7064_TOGGLE_GRAPH_FOR_CARGO;
   750 				 * exist then the local player has used the climate cheat or
   641 
   751 				 * changed the NewGRF configuration with this window open. */
   642 			if (!HasBit(_legend_excluded_cargo, i)) this->LowerWidget(i + 3);
   752 				if (i + 3 < w->widget_count) {
   643 		}
   753 					/* Since the buttons have no text, no images,
   644 
   754 					 * both the text and the colored box have to be manually painted.
   645 		this->SetDirty();
   755 					 * clk_dif will move one pixel down and one pixel to the right
   646 
   756 					 * when the button is clicked */
   647 		this->gd_height = this->height - 38;
   757 					byte clk_dif = w->IsWidgetLowered(i + 3) ? 1 : 0;
   648 		this->num_on_x_axis = 20;
   758 
   649 		this->num_vert_lines = 20;
   759 					GfxFillRect(x + clk_dif, y + clk_dif, x + 8 + clk_dif, y + 5 + clk_dif, 0);
   650 		this->month = 0xFF;
   760 					GfxFillRect(x + 1 + clk_dif, y + 1 + clk_dif, x + 7 + clk_dif, y + 4 + clk_dif, cs->legend_colour);
   651 		this->x_values_start     = 10;
   761 					SetDParam(0, cs->name);
   652 		this->x_values_increment = 10;
   762 					DrawString(x + 14 + clk_dif, y + clk_dif, STR_7065, TC_FROMSTRING);
   653 
   763 					y += 8;
   654 		this->FindWindowPlacementAndResize(desc);
   764 				}
   655 	}
   765 
   656 
   766 				gd.colors[i] = cs->legend_colour;
   657 	virtual void OnPaint()
   767 				for (uint j = 0; j != 20; j++) {
   658 	{
   768 					gd.cost[i][j] = GetTransportedGoodsIncome(10, 20, j * 6 + 6, c);
   659 		this->DrawWidgets();
   769 				}
   660 
   770 
   661 		this->excluded_data = _legend_excluded_cargo;
   771 				i++;
   662 
   772 			}
   663 		int x = 495;
   773 			gd.num_dataset = i;
   664 		int y = 24;
   774 
   665 
   775 			DrawGraph(&gd);
   666 		uint i = 0;
   776 
   667 		for (CargoID c = 0; c < NUM_CARGO; c++) {
   777 			DrawString(2 + 46, 24 + gd.height + 7, STR_7062_DAYS_IN_TRANSIT, TC_FROMSTRING);
   668 			const CargoSpec *cs = GetCargo(c);
   778 			DrawString(2 + 84, 24 - 9, STR_7063_PAYMENT_FOR_DELIVERING, TC_FROMSTRING);
   669 			if (!cs->IsValid()) continue;
   779 			break;
   670 
   780 		}
   671 			/* Only draw labels for widgets that exist. If the widget doesn't
   781 
   672 				* exist then the local player has used the climate cheat or
   782 		case WE_CLICK:
   673 				* changed the NewGRF configuration with this window open. */
   783 			if (e->we.click.widget >= 3) {
   674 			if (i + 3 < this->widget_count) {
   784 				ToggleBit(_legend_excluded_cargo, e->we.click.widget - 3);
   675 				/* Since the buttons have no text, no images,
   785 				w->ToggleWidgetLoweredState(e->we.click.widget);
   676 					* both the text and the colored box have to be manually painted.
   786 				w->SetDirty();
   677 					* clk_dif will move one pixel down and one pixel to the right
   787 			}
   678 					* when the button is clicked */
   788 			break;
   679 				byte clk_dif = this->IsWidgetLowered(i + 3) ? 1 : 0;
   789 	}
   680 
   790 }
   681 				GfxFillRect(x + clk_dif, y + clk_dif, x + 8 + clk_dif, y + 5 + clk_dif, 0);
       
   682 				GfxFillRect(x + 1 + clk_dif, y + 1 + clk_dif, x + 7 + clk_dif, y + 4 + clk_dif, cs->legend_colour);
       
   683 				SetDParam(0, cs->name);
       
   684 				DrawString(x + 14 + clk_dif, y + clk_dif, STR_7065, TC_FROMSTRING);
       
   685 				y += 8;
       
   686 			}
       
   687 
       
   688 			this->colors[i] = cs->legend_colour;
       
   689 			for (uint j = 0; j != 20; j++) {
       
   690 				this->cost[i][j] = GetTransportedGoodsIncome(10, 20, j * 6 + 6, c);
       
   691 			}
       
   692 
       
   693 			i++;
       
   694 		}
       
   695 		this->num_dataset = i;
       
   696 
       
   697 		this->DrawGraph();
       
   698 
       
   699 		DrawString(2 + 46, 24 + this->gd_height + 7, STR_7062_DAYS_IN_TRANSIT, TC_FROMSTRING);
       
   700 		DrawString(2 + 84, 24 - 9, STR_7063_PAYMENT_FOR_DELIVERING, TC_FROMSTRING);
       
   701 	}
       
   702 
       
   703 	virtual void OnClick(Point pt, int widget)
       
   704 	{
       
   705 		if (widget >= 3) {
       
   706 			ToggleBit(_legend_excluded_cargo, widget - 3);
       
   707 			this->ToggleWidgetLoweredState(widget);
       
   708 			this->SetDirty();
       
   709 		}
       
   710 	}
       
   711 };
   791 
   712 
   792 static const Widget _cargo_payment_rates_widgets[] = {
   713 static const Widget _cargo_payment_rates_widgets[] = {
   793 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                     STR_018B_CLOSE_WINDOW},
   714 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                     STR_018B_CLOSE_WINDOW},
   794 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   567,     0,    13, STR_7061_CARGO_PAYMENT_RATES, STR_018C_WINDOW_TITLE_DRAG_THIS},
   715 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   567,     0,    13, STR_7061_CARGO_PAYMENT_RATES, STR_018C_WINDOW_TITLE_DRAG_THIS},
   795 {      WWT_PANEL, RESIZE_BOTTOM,    14,     0,   567,    14,    45, 0x0,                          STR_NULL},
   716 {      WWT_PANEL, RESIZE_BOTTOM,    14,     0,   567,    14,    45, 0x0,                          STR_NULL},
   799 static const WindowDesc _cargo_payment_rates_desc = {
   720 static const WindowDesc _cargo_payment_rates_desc = {
   800 	WDP_AUTO, WDP_AUTO, 568, 46, 568, 46,
   721 	WDP_AUTO, WDP_AUTO, 568, 46, 568, 46,
   801 	WC_PAYMENT_RATES, WC_NONE,
   722 	WC_PAYMENT_RATES, WC_NONE,
   802 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
   723 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
   803 	_cargo_payment_rates_widgets,
   724 	_cargo_payment_rates_widgets,
   804 	CargoPaymentRatesWndProc
       
   805 };
   725 };
   806 
   726 
   807 
   727 
   808 void ShowCargoPaymentRates()
   728 void ShowCargoPaymentRates()
   809 {
   729 {
   810 	Window *w = AllocateWindowDescFront<Window>(&_cargo_payment_rates_desc, 0);
   730 	AllocateWindowDescFront<PaymentRatesGraphWindow>(&_cargo_payment_rates_desc, 0);
   811 	if (w == NULL) return;
       
   812 
       
   813 	/* Count the number of active cargo types */
       
   814 	uint num_active = 0;
       
   815 	for (CargoID c = 0; c < NUM_CARGO; c++) {
       
   816 		if (GetCargo(c)->IsValid()) num_active++;
       
   817 	}
       
   818 
       
   819 	/* Resize the window to fit the cargo types */
       
   820 	ResizeWindow(w, 0, max(num_active, 12U) * 8);
       
   821 
       
   822 	/* Add widgets for each cargo type */
       
   823 	w->widget_count += num_active;
       
   824 	w->widget = ReallocT(w->widget, w->widget_count + 1);
       
   825 	w->widget[w->widget_count].type = WWT_LAST;
       
   826 
       
   827 	/* Set the properties of each widget */
       
   828 	for (uint i = 0; i != num_active; i++) {
       
   829 		Widget *wi = &w->widget[3 + i];
       
   830 		wi->type     = WWT_PANEL;
       
   831 		wi->display_flags = RESIZE_NONE;
       
   832 		wi->color    = 12;
       
   833 		wi->left     = 493;
       
   834 		wi->right    = 562;
       
   835 		wi->top      = 24 + i * 8;
       
   836 		wi->bottom   = wi->top + 7;
       
   837 		wi->data     = 0;
       
   838 		wi->tooltips = STR_7064_TOGGLE_GRAPH_FOR_CARGO;
       
   839 
       
   840 		if (!HasBit(_legend_excluded_cargo, i)) w->LowerWidget(i + 3);
       
   841 	}
       
   842 
       
   843 	w->SetDirty();
       
   844 }
   731 }
   845 
   732 
   846 /************************/
   733 /************************/
   847 /* COMPANY LEAGUE TABLE */
   734 /* COMPANY LEAGUE TABLE */
   848 /************************/
   735 /************************/
   877 	const Player* p2 = *(const Player* const*)elem2;
   764 	const Player* p2 = *(const Player* const*)elem2;
   878 
   765 
   879 	return p2->old_economy[1].performance_history - p1->old_economy[1].performance_history;
   766 	return p2->old_economy[1].performance_history - p1->old_economy[1].performance_history;
   880 }
   767 }
   881 
   768 
   882 static void CompanyLeagueWndProc(Window *w, WindowEvent *e)
   769 struct CompanyLeagueWindow : Window {
   883 {
   770 	CompanyLeagueWindow(const WindowDesc *desc, WindowNumber window_number) : Window(desc, window_number)
   884 	switch (e->event) {
   771 	{
   885 		case WE_PAINT: {
   772 	}
   886 			const Player* plist[MAX_PLAYERS];
   773 
   887 			const Player* p;
   774 	virtual void OnPaint()
   888 
   775 	{
   889 			DrawWindowWidgets(w);
   776 		const Player *plist[MAX_PLAYERS];
   890 
   777 		const Player *p;
   891 			uint pl_num = 0;
   778 
   892 			FOR_ALL_PLAYERS(p) if (p->is_active) plist[pl_num++] = p;
   779 		this->DrawWidgets();
   893 
   780 
   894 			qsort((void*)plist, pl_num, sizeof(*plist), PerfHistComp);
   781 		uint pl_num = 0;
   895 
   782 		FOR_ALL_PLAYERS(p) if (p->is_active) plist[pl_num++] = p;
   896 			for (uint i = 0; i != pl_num; i++) {
   783 
   897 				p = plist[i];
   784 		qsort((void*)plist, pl_num, sizeof(*plist), PerfHistComp);
   898 				SetDParam(0, i + STR_01AC_1ST);
   785 
   899 				SetDParam(1, p->index);
   786 		for (uint i = 0; i != pl_num; i++) {
   900 				SetDParam(2, p->index);
   787 			p = plist[i];
   901 				SetDParam(3, GetPerformanceTitleFromValue(p->old_economy[1].performance_history));
   788 			SetDParam(0, i + STR_01AC_1ST);
   902 
   789 			SetDParam(1, p->index);
   903 				DrawString(2, 15 + i * 10, i == 0 ? STR_7054 : STR_7055, TC_FROMSTRING);
   790 			SetDParam(2, p->index);
   904 				DrawPlayerIcon(p->index, 27, 16 + i * 10);
   791 			SetDParam(3, GetPerformanceTitleFromValue(p->old_economy[1].performance_history));
   905 			}
   792 
   906 
   793 			DrawString(2, 15 + i * 10, i == 0 ? STR_7054 : STR_7055, TC_FROMSTRING);
   907 			break;
   794 			DrawPlayerIcon(p->index, 27, 16 + i * 10);
   908 		}
   795 		}
   909 	}
   796 	}
   910 }
   797 };
   911 
   798 
   912 
   799 
   913 static const Widget _company_league_widgets[] = {
   800 static const Widget _company_league_widgets[] = {
   914 {   WWT_CLOSEBOX, RESIZE_NONE, 14,   0,  10,  0, 13, STR_00C5,                      STR_018B_CLOSE_WINDOW},
   801 {   WWT_CLOSEBOX, RESIZE_NONE, 14,   0,  10,  0, 13, STR_00C5,                      STR_018B_CLOSE_WINDOW},
   915 {    WWT_CAPTION, RESIZE_NONE, 14,  11, 387,  0, 13, STR_7053_COMPANY_LEAGUE_TABLE, STR_018C_WINDOW_TITLE_DRAG_THIS},
   802 {    WWT_CAPTION, RESIZE_NONE, 14,  11, 387,  0, 13, STR_7053_COMPANY_LEAGUE_TABLE, STR_018C_WINDOW_TITLE_DRAG_THIS},
   921 static const WindowDesc _company_league_desc = {
   808 static const WindowDesc _company_league_desc = {
   922 	WDP_AUTO, WDP_AUTO, 400, 97, 400, 97,
   809 	WDP_AUTO, WDP_AUTO, 400, 97, 400, 97,
   923 	WC_COMPANY_LEAGUE, WC_NONE,
   810 	WC_COMPANY_LEAGUE, WC_NONE,
   924 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON,
   811 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON,
   925 	_company_league_widgets,
   812 	_company_league_widgets,
   926 	CompanyLeagueWndProc
       
   927 };
   813 };
   928 
   814 
   929 void ShowCompanyLeagueTable()
   815 void ShowCompanyLeagueTable()
   930 {
   816 {
   931 	AllocateWindowDescFront<Window>(&_company_league_desc, 0);
   817 	AllocateWindowDescFront<CompanyLeagueWindow>(&_company_league_desc, 0);
   932 }
   818 }
   933 
   819 
   934 /*****************************/
   820 /*****************************/
   935 /* PERFORMANCE RATING DETAIL */
   821 /* PERFORMANCE RATING DETAIL */
   936 /*****************************/
   822 /*****************************/
   937 
   823 
   938 static void PerformanceRatingDetailWndProc(Window *w, WindowEvent *e)
   824 struct PerformanceRatingDetailWindow : Window {
   939 {
   825 	static PlayerID player;
   940 	static PlayerID _performance_rating_detail_player = INVALID_PLAYER;
   826 	int timeout;
   941 
   827 
   942 	switch (e->event) {
   828 	PerformanceRatingDetailWindow(const WindowDesc *desc, WindowNumber window_number) : Window(desc, window_number)
   943 		case WE_PAINT: {
   829 	{
   944 			byte x;
   830 		/* Disable the players who are not active */
   945 			uint16 y = 14;
   831 		for (PlayerID i = PLAYER_FIRST; i < MAX_PLAYERS; i++) {
   946 			int total_score = 0;
   832 			this->SetWidgetDisabledState(i + 13, !GetPlayer(i)->is_active);
   947 			int color_done, color_notdone;
   833 		}
   948 
   834 
   949 			/* Draw standard stuff */
   835 		this->UpdatePlayerStats();
   950 			DrawWindowWidgets(w);
   836 
   951 
   837 		if (player != INVALID_PLAYER) this->LowerWidget(player + 13);
   952 			/* Check if the currently selected player is still active. */
   838 
   953 			if (_performance_rating_detail_player == INVALID_PLAYER || !GetPlayer(_performance_rating_detail_player)->is_active) {
   839 		this->FindWindowPlacementAndResize(desc);
   954 				if (_performance_rating_detail_player != INVALID_PLAYER) {
   840 	}
   955 					/* Raise and disable the widget for the previous selection. */
   841 
   956 					w->RaiseWidget(_performance_rating_detail_player + 13);
   842 	void UpdatePlayerStats()
   957 					w->DisableWidget(_performance_rating_detail_player + 13);
   843 	{
   958 					w->SetDirty();
   844 		/* Update all player stats with the current data
   959 
   845 		 * (this is because _score_info is not saved to a savegame) */
   960 					_performance_rating_detail_player = INVALID_PLAYER;
   846 		Player *p;
       
   847 		FOR_ALL_PLAYERS(p) {
       
   848 			if (p->is_active) UpdateCompanyRatingAndValue(p, false);
       
   849 		}
       
   850 
       
   851 		this->timeout = DAY_TICKS * 5;
       
   852 
       
   853 	}
       
   854 
       
   855 	virtual void OnPaint()
       
   856 	{
       
   857 		byte x;
       
   858 		uint16 y = 14;
       
   859 		int total_score = 0;
       
   860 		int color_done, color_notdone;
       
   861 
       
   862 		/* Draw standard stuff */
       
   863 		this->DrawWidgets();
       
   864 
       
   865 		/* Check if the currently selected player is still active. */
       
   866 		if (player == INVALID_PLAYER || !GetPlayer(player)->is_active) {
       
   867 			if (player != INVALID_PLAYER) {
       
   868 				/* Raise and disable the widget for the previous selection. */
       
   869 				this->RaiseWidget(player + 13);
       
   870 				this->DisableWidget(player + 13);
       
   871 				this->SetDirty();
       
   872 
       
   873 				player = INVALID_PLAYER;
       
   874 			}
       
   875 
       
   876 			for (PlayerID i = PLAYER_FIRST; i < MAX_PLAYERS; i++) {
       
   877 				if (GetPlayer(i)->is_active) {
       
   878 					/* Lower the widget corresponding to this player. */
       
   879 					this->LowerWidget(i + 13);
       
   880 					this->SetDirty();
       
   881 
       
   882 					player = i;
       
   883 					break;
   961 				}
   884 				}
   962 
   885 			}
   963 				for (PlayerID i = PLAYER_FIRST; i < MAX_PLAYERS; i++) {
   886 		}
   964 					if (GetPlayer(i)->is_active) {
   887 
   965 						/* Lower the widget corresponding to this player. */
   888 		/* If there are no active players, don't display anything else. */
   966 						w->LowerWidget(i + 13);
   889 		if (player == INVALID_PLAYER) return;
   967 						w->SetDirty();
   890 
   968 
   891 		/* Paint the player icons */
   969 						_performance_rating_detail_player = i;
   892 		for (PlayerID i = PLAYER_FIRST; i < MAX_PLAYERS; i++) {
   970 						break;
   893 			if (!GetPlayer(i)->is_active) {
   971 					}
   894 				/* Check if we have the player as an active player */
       
   895 				if (!this->IsWidgetDisabled(i + 13)) {
       
   896 					/* Bah, player gone :( */
       
   897 					this->DisableWidget(i + 13);
       
   898 
       
   899 					/* We need a repaint */
       
   900 					this->SetDirty();
   972 				}
   901 				}
   973 			}
   902 				continue;
   974 
   903 			}
   975 			/* If there are no active players, don't display anything else. */
   904 
   976 			if (_performance_rating_detail_player == INVALID_PLAYER) break;
   905 			/* Check if we have the player marked as inactive */
   977 
   906 			if (this->IsWidgetDisabled(i + 13)) {
   978 			/* Paint the player icons */
   907 				/* New player! Yippie :p */
   979 			for (PlayerID i = PLAYER_FIRST; i < MAX_PLAYERS; i++) {
   908 				this->EnableWidget(i + 13);
   980 				if (!GetPlayer(i)->is_active) {
   909 				/* We need a repaint */
   981 					/* Check if we have the player as an active player */
   910 				this->SetDirty();
   982 					if (!w->IsWidgetDisabled(i + 13)) {
   911 			}
   983 						/* Bah, player gone :( */
   912 
   984 						w->DisableWidget(i + 13);
   913 			x = (i == player) ? 1 : 0;
   985 
   914 			DrawPlayerIcon(i, i * 37 + 13 + x, 16 + x);
   986 						/* We need a repaint */
   915 		}
   987 						w->SetDirty();
   916 
   988 					}
   917 		/* The colors used to show how the progress is going */
   989 					continue;
   918 		color_done = _colour_gradient[COLOUR_GREEN][4];
   990 				}
   919 		color_notdone = _colour_gradient[COLOUR_RED][4];
   991 
   920 
   992 				/* Check if we have the player marked as inactive */
   921 		/* Draw all the score parts */
   993 				if (w->IsWidgetDisabled(i + 13)) {
   922 		for (ScoreID i = SCORE_BEGIN; i < SCORE_END; i++) {
   994 					/* New player! Yippie :p */
   923 			int val    = _score_part[player][i];
   995 					w->EnableWidget(i + 13);
   924 			int needed = _score_info[i].needed;
   996 					/* We need a repaint */
   925 			int score  = _score_info[i].score;
   997 					w->SetDirty();
   926 
   998 				}
   927 			y += 20;
   999 
   928 			/* SCORE_TOTAL has his own rulez ;) */
  1000 				x = (i == _performance_rating_detail_player) ? 1 : 0;
   929 			if (i == SCORE_TOTAL) {
  1001 				DrawPlayerIcon(i, i * 37 + 13 + x, 16 + x);
   930 				needed = total_score;
  1002 			}
   931 				score = SCORE_MAX;
  1003 
   932 			} else {
  1004 			/* The colors used to show how the progress is going */
   933 				total_score += score;
  1005 			color_done = _colour_gradient[COLOUR_GREEN][4];
   934 			}
  1006 			color_notdone = _colour_gradient[COLOUR_RED][4];
   935 
  1007 
   936 			DrawString(7, y, STR_PERFORMANCE_DETAIL_VEHICLES + i, TC_FROMSTRING);
  1008 			/* Draw all the score parts */
   937 
  1009 			for (ScoreID i = SCORE_BEGIN; i < SCORE_END; i++) {
   938 			/* Draw the score */
  1010 				int val    = _score_part[_performance_rating_detail_player][i];
   939 			SetDParam(0, score);
  1011 				int needed = _score_info[i].needed;
   940 			DrawStringRightAligned(107, y, SET_PERFORMANCE_DETAIL_INT, TC_FROMSTRING);
  1012 				int score  = _score_info[i].score;
   941 
  1013 
   942 			/* Calculate the %-bar */
  1014 				y += 20;
   943 			x = Clamp(val, 0, needed) * 50 / needed;
  1015 				/* SCORE_TOTAL has his own rulez ;) */
   944 
  1016 				if (i == SCORE_TOTAL) {
   945 			/* SCORE_LOAN is inversed */
  1017 					needed = total_score;
   946 			if (val < 0 && i == SCORE_LOAN) x = 0;
  1018 					score = SCORE_MAX;
   947 
  1019 				} else {
   948 			/* Draw the bar */
  1020 					total_score += score;
   949 			if (x !=  0) GfxFillRect(112,     y - 2, 112 + x,  y + 10, color_done);
  1021 				}
   950 			if (x != 50) GfxFillRect(112 + x, y - 2, 112 + 50, y + 10, color_notdone);
  1022 
   951 
  1023 				DrawString(7, y, STR_PERFORMANCE_DETAIL_VEHICLES + i, TC_FROMSTRING);
   952 			/* Calculate the % */
  1024 
   953 			x = Clamp(val, 0, needed) * 100 / needed;
  1025 				/* Draw the score */
   954 
  1026 				SetDParam(0, score);
   955 			/* SCORE_LOAN is inversed */
  1027 				DrawStringRightAligned(107, y, SET_PERFORMANCE_DETAIL_INT, TC_FROMSTRING);
   956 			if (val < 0 && i == SCORE_LOAN) x = 0;
  1028 
   957 
  1029 				/* Calculate the %-bar */
   958 			/* Draw it */
  1030 				x = Clamp(val, 0, needed) * 50 / needed;
   959 			SetDParam(0, x);
  1031 
   960 			DrawStringCentered(137, y, STR_PERFORMANCE_DETAIL_PERCENT, TC_FROMSTRING);
  1032 				/* SCORE_LOAN is inversed */
   961 
  1033 				if (val < 0 && i == SCORE_LOAN) x = 0;
   962 			/* SCORE_LOAN is inversed */
  1034 
   963 			if (i == SCORE_LOAN) val = needed - val;
  1035 				/* Draw the bar */
   964 
  1036 				if (x !=  0) GfxFillRect(112,     y - 2, 112 + x,  y + 10, color_done);
   965 			/* Draw the amount we have against what is needed
  1037 				if (x != 50) GfxFillRect(112 + x, y - 2, 112 + 50, y + 10, color_notdone);
   966 				* For some of them it is in currency format */
  1038 
   967 			SetDParam(0, val);
  1039 				/* Calculate the % */
   968 			SetDParam(1, needed);
  1040 				x = Clamp(val, 0, needed) * 100 / needed;
   969 			switch (i) {
  1041 
   970 				case SCORE_MIN_PROFIT:
  1042 				/* SCORE_LOAN is inversed */
   971 				case SCORE_MIN_INCOME:
  1043 				if (val < 0 && i == SCORE_LOAN) x = 0;
   972 				case SCORE_MAX_INCOME:
  1044 
   973 				case SCORE_MONEY:
  1045 				/* Draw it */
   974 				case SCORE_LOAN:
  1046 				SetDParam(0, x);
   975 					DrawString(167, y, STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, TC_FROMSTRING);
  1047 				DrawStringCentered(137, y, STR_PERFORMANCE_DETAIL_PERCENT, TC_FROMSTRING);
   976 					break;
  1048 
   977 				default:
  1049 				/* SCORE_LOAN is inversed */
   978 					DrawString(167, y, STR_PERFORMANCE_DETAIL_AMOUNT_INT, TC_FROMSTRING);
  1050 				if (i == SCORE_LOAN) val = needed - val;
   979 			}
  1051 
   980 		}
  1052 				/* Draw the amount we have against what is needed
   981 	}
  1053 				 * For some of them it is in currency format */
   982 
  1054 				SetDParam(0, val);
   983 	virtual void OnClick(Point pt, int widget)
  1055 				SetDParam(1, needed);
   984 	{
  1056 				switch (i) {
   985 		/* Check which button is clicked */
  1057 					case SCORE_MIN_PROFIT:
   986 		if (IsInsideMM(widget, 13, 21)) {
  1058 					case SCORE_MIN_INCOME:
   987 			/* Is it no on disable? */
  1059 					case SCORE_MAX_INCOME:
   988 			if (!this->IsWidgetDisabled(widget)) {
  1060 					case SCORE_MONEY:
   989 				this->RaiseWidget(player + 13);
  1061 					case SCORE_LOAN:
   990 				player = (PlayerID)(widget - 13);
  1062 						DrawString(167, y, STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, TC_FROMSTRING);
   991 				this->LowerWidget(player + 13);
  1063 						break;
   992 				this->SetDirty();
  1064 					default:
   993 			}
  1065 						DrawString(167, y, STR_PERFORMANCE_DETAIL_AMOUNT_INT, TC_FROMSTRING);
   994 		}
  1066 				}
   995 	}
  1067 			}
   996 
  1068 
   997 	virtual void OnTick()
  1069 			break;
   998 	{
  1070 		}
   999 		if (_pause_game != 0) return;
  1071 
  1000 
  1072 		case WE_CLICK:
  1001 		/* Update the player score every 5 days */
  1073 			/* Check which button is clicked */
  1002 		if (--this->timeout == 0) {
  1074 			if (IsInsideMM(e->we.click.widget, 13, 21)) {
  1003 			this->UpdatePlayerStats();
  1075 				/* Is it no on disable? */
  1004 			this->SetDirty();
  1076 				if (!w->IsWidgetDisabled(e->we.click.widget)) {
  1005 		}
  1077 					w->RaiseWidget(_performance_rating_detail_player + 13);
  1006 	}
  1078 					_performance_rating_detail_player = (PlayerID)(e->we.click.widget - 13);
  1007 };
  1079 					w->LowerWidget(_performance_rating_detail_player + 13);
  1008 
  1080 					w->SetDirty();
  1009 PlayerID PerformanceRatingDetailWindow::player = INVALID_PLAYER;
  1081 				}
  1010 
  1082 			}
       
  1083 			break;
       
  1084 
       
  1085 		case WE_CREATE: {
       
  1086 			Player *p2;
       
  1087 
       
  1088 			/* Disable the players who are not active */
       
  1089 			for (PlayerID i = PLAYER_FIRST; i < MAX_PLAYERS; i++) {
       
  1090 				w->SetWidgetDisabledState(i + 13, !GetPlayer(i)->is_active);
       
  1091 			}
       
  1092 			/* Update all player stats with the current data
       
  1093 			 * (this is because _score_info is not saved to a savegame) */
       
  1094 			FOR_ALL_PLAYERS(p2) {
       
  1095 				if (p2->is_active) UpdateCompanyRatingAndValue(p2, false);
       
  1096 			}
       
  1097 
       
  1098 			w->custom[0] = DAY_TICKS;
       
  1099 			w->custom[1] = 5;
       
  1100 
       
  1101 			if (_performance_rating_detail_player != INVALID_PLAYER) w->LowerWidget(_performance_rating_detail_player + 13);
       
  1102 			w->SetDirty();
       
  1103 
       
  1104 			break;
       
  1105 		}
       
  1106 
       
  1107 		case WE_TICK:
       
  1108 			if (_pause_game != 0) break;
       
  1109 
       
  1110 			/* Update the player score every 5 days */
       
  1111 			if (--w->custom[0] == 0) {
       
  1112 				w->custom[0] = DAY_TICKS;
       
  1113 				if (--w->custom[1] == 0) {
       
  1114 					Player *p2;
       
  1115 
       
  1116 					w->custom[1] = 5;
       
  1117 					FOR_ALL_PLAYERS(p2) {
       
  1118 						/* Skip if player is not active */
       
  1119 						if (p2->is_active) UpdateCompanyRatingAndValue(p2, false);
       
  1120 					}
       
  1121 					w->SetDirty();
       
  1122 				}
       
  1123 			}
       
  1124 
       
  1125 			break;
       
  1126 	}
       
  1127 }
       
  1128 
  1011 
  1129 static const Widget _performance_rating_detail_widgets[] = {
  1012 static const Widget _performance_rating_detail_widgets[] = {
  1130 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,               STR_018B_CLOSE_WINDOW},
  1013 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,               STR_018B_CLOSE_WINDOW},
  1131 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   298,     0,    13, STR_PERFORMANCE_DETAIL, STR_018C_WINDOW_TITLE_DRAG_THIS},
  1014 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   298,     0,    13, STR_PERFORMANCE_DETAIL, STR_018C_WINDOW_TITLE_DRAG_THIS},
  1132 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   298,    14,    27, 0x0,                    STR_NULL},
  1015 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   298,    14,    27, 0x0,                    STR_NULL},
  1156 static const WindowDesc _performance_rating_detail_desc = {
  1039 static const WindowDesc _performance_rating_detail_desc = {
  1157 	WDP_AUTO, WDP_AUTO, 299, 228, 299, 228,
  1040 	WDP_AUTO, WDP_AUTO, 299, 228, 299, 228,
  1158 	WC_PERFORMANCE_DETAIL, WC_NONE,
  1041 	WC_PERFORMANCE_DETAIL, WC_NONE,
  1159 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
  1042 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
  1160 	_performance_rating_detail_widgets,
  1043 	_performance_rating_detail_widgets,
  1161 	PerformanceRatingDetailWndProc
       
  1162 };
  1044 };
  1163 
  1045 
  1164 void ShowPerformanceRatingDetail()
  1046 void ShowPerformanceRatingDetail()
  1165 {
  1047 {
  1166 	AllocateWindowDescFront<Window>(&_performance_rating_detail_desc, 0);
  1048 	AllocateWindowDescFront<PerformanceRatingDetailWindow>(&_performance_rating_detail_desc, 0);
  1167 }
  1049 }