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}, |
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}, |