Update: Translations from eints
[openttd-github.git] / src / linkgraph / linkgraph_gui.cpp
blob100e6d62739dae259288d13ffbde85ca65777234
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file linkgraph_gui.cpp Implementation of linkgraph overlay GUI. */
10 #include "../stdafx.h"
11 #include "../window_gui.h"
12 #include "../window_func.h"
13 #include "../company_base.h"
14 #include "../company_gui.h"
15 #include "../timer/timer_game_tick.h"
16 #include "../timer/timer_game_calendar.h"
17 #include "../viewport_func.h"
18 #include "../zoom_func.h"
19 #include "../smallmap_gui.h"
20 #include "../core/geometry_func.hpp"
21 #include "../widgets/link_graph_legend_widget.h"
22 #include "../strings_func.h"
23 #include "linkgraph_gui.h"
25 #include "table/strings.h"
27 #include "../safeguards.h"
29 /**
30 * Colours for the various "load" states of links. Ordered from "unused" to
31 * "overloaded".
33 const uint8_t LinkGraphOverlay::LINK_COLOURS[][12] = {
35 0x0f, 0xd1, 0xd0, 0x57,
36 0x55, 0x53, 0xbf, 0xbd,
37 0xba, 0xb9, 0xb7, 0xb5
40 0x0f, 0xd1, 0xd0, 0x57,
41 0x55, 0x53, 0x96, 0x95,
42 0x94, 0x93, 0x92, 0x91
45 0x0f, 0x0b, 0x09, 0x07,
46 0x05, 0x03, 0xbf, 0xbd,
47 0xba, 0xb9, 0xb7, 0xb5
50 0x0f, 0x0b, 0x0a, 0x09,
51 0x08, 0x07, 0x06, 0x05,
52 0x04, 0x03, 0x02, 0x01
56 /**
57 * Get a DPI for the widget we will be drawing to.
58 * @param dpi DrawPixelInfo to fill with the desired dimensions.
60 void LinkGraphOverlay::GetWidgetDpi(DrawPixelInfo *dpi) const
62 const NWidgetBase *wi = this->window->GetWidget<NWidgetBase>(this->widget_id);
63 dpi->left = dpi->top = 0;
64 dpi->width = wi->current_x;
65 dpi->height = wi->current_y;
68 /**
69 * Rebuild the cache and recalculate which links and stations to be shown.
71 void LinkGraphOverlay::RebuildCache()
73 this->cached_links.clear();
74 this->cached_stations.clear();
75 if (this->company_mask == 0) return;
77 DrawPixelInfo dpi;
78 this->GetWidgetDpi(&dpi);
80 for (const Station *sta : Station::Iterate()) {
81 if (sta->rect.IsEmpty()) continue;
83 Point pta = this->GetStationMiddle(sta);
85 StationID from = sta->index;
86 StationLinkMap &seen_links = this->cached_links[from];
88 uint supply = 0;
89 for (CargoID c : SetCargoBitIterator(this->cargo_mask)) {
90 if (!CargoSpec::Get(c)->IsValid()) continue;
91 if (!LinkGraph::IsValidID(sta->goods[c].link_graph)) continue;
92 const LinkGraph &lg = *LinkGraph::Get(sta->goods[c].link_graph);
94 ConstNode &from_node = lg[sta->goods[c].node];
95 supply += lg.Monthly(from_node.supply);
96 for (const Edge &edge : from_node.edges) {
97 StationID to = lg[edge.dest_node].station;
98 assert(from != to);
99 if (!Station::IsValidID(to) || seen_links.find(to) != seen_links.end()) {
100 continue;
102 const Station *stb = Station::Get(to);
103 assert(sta != stb);
105 /* Show links between stations of selected companies or "neutral" ones like oilrigs. */
106 if (stb->owner != OWNER_NONE && sta->owner != OWNER_NONE && !HasBit(this->company_mask, stb->owner)) continue;
107 if (stb->rect.IsEmpty()) continue;
109 if (!this->IsLinkVisible(pta, this->GetStationMiddle(stb), &dpi)) continue;
111 this->AddLinks(sta, stb);
112 seen_links[to]; // make sure it is created and marked as seen
115 if (this->IsPointVisible(pta, &dpi)) {
116 this->cached_stations.emplace_back(from, supply);
122 * Determine if a certain point is inside the given DPI, with some lee way.
123 * @param pt Point we are looking for.
124 * @param dpi Visible area.
125 * @param padding Extent of the point.
126 * @return If the point or any of its 'extent' is inside the dpi.
128 inline bool LinkGraphOverlay::IsPointVisible(Point pt, const DrawPixelInfo *dpi, int padding) const
130 return pt.x > dpi->left - padding && pt.y > dpi->top - padding &&
131 pt.x < dpi->left + dpi->width + padding &&
132 pt.y < dpi->top + dpi->height + padding;
136 * Determine if a certain link crosses through the area given by the dpi with some lee way.
137 * @param pta First end of the link.
138 * @param ptb Second end of the link.
139 * @param dpi Visible area.
140 * @param padding Width or thickness of the link.
141 * @return If the link or any of its "thickness" is visible. This may return false positives.
143 inline bool LinkGraphOverlay::IsLinkVisible(Point pta, Point ptb, const DrawPixelInfo *dpi, int padding) const
145 const int left = dpi->left - padding;
146 const int right = dpi->left + dpi->width + padding;
147 const int top = dpi->top - padding;
148 const int bottom = dpi->top + dpi->height + padding;
151 * This method is an implementation of the Cohen-Sutherland line-clipping algorithm.
152 * See: https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
155 const uint8_t INSIDE = 0; // 0000
156 const uint8_t LEFT = 1; // 0001
157 const uint8_t RIGHT = 2; // 0010
158 const uint8_t BOTTOM = 4; // 0100
159 const uint8_t TOP = 8; // 1000
161 int x0 = pta.x;
162 int y0 = pta.y;
163 int x1 = ptb.x;
164 int y1 = ptb.y;
166 auto out_code = [&](int x, int y) -> uint8_t {
167 uint8_t out = INSIDE;
168 if (x < left) {
169 out |= LEFT;
170 } else if (x > right) {
171 out |= RIGHT;
173 if (y < top) {
174 out |= TOP;
175 } else if (y > bottom) {
176 out |= BOTTOM;
178 return out;
181 uint8_t c0 = out_code(x0, y0);
182 uint8_t c1 = out_code(x1, y1);
184 while (true) {
185 if (c0 == 0 || c1 == 0) return true;
186 if ((c0 & c1) != 0) return false;
188 if (c0 & TOP) { // point 0 is above the clip window
189 x0 = x0 + (int)(((int64_t) (x1 - x0)) * ((int64_t) (top - y0)) / ((int64_t) (y1 - y0)));
190 y0 = top;
191 } else if (c0 & BOTTOM) { // point 0 is below the clip window
192 x0 = x0 + (int)(((int64_t) (x1 - x0)) * ((int64_t) (bottom - y0)) / ((int64_t) (y1 - y0)));
193 y0 = bottom;
194 } else if (c0 & RIGHT) { // point 0 is to the right of clip window
195 y0 = y0 + (int)(((int64_t) (y1 - y0)) * ((int64_t) (right - x0)) / ((int64_t) (x1 - x0)));
196 x0 = right;
197 } else if (c0 & LEFT) { // point 0 is to the left of clip window
198 y0 = y0 + (int)(((int64_t) (y1 - y0)) * ((int64_t) (left - x0)) / ((int64_t) (x1 - x0)));
199 x0 = left;
202 c0 = out_code(x0, y0);
205 NOT_REACHED();
209 * Add all "interesting" links between the given stations to the cache.
210 * @param from The source station.
211 * @param to The destination station.
213 void LinkGraphOverlay::AddLinks(const Station *from, const Station *to)
215 for (CargoID c : SetCargoBitIterator(this->cargo_mask)) {
216 if (!CargoSpec::Get(c)->IsValid()) continue;
217 const GoodsEntry &ge = from->goods[c];
218 if (!LinkGraph::IsValidID(ge.link_graph) ||
219 ge.link_graph != to->goods[c].link_graph) {
220 continue;
222 const LinkGraph &lg = *LinkGraph::Get(ge.link_graph);
223 if (lg[ge.node].HasEdgeTo(to->goods[c].node)) {
224 ConstEdge &edge = lg[ge.node][to->goods[c].node];
225 this->AddStats(c, lg.Monthly(edge.capacity), lg.Monthly(edge.usage),
226 ge.flows.GetFlowVia(to->index),
227 edge.TravelTime() / Ticks::DAY_TICKS,
228 from->owner == OWNER_NONE || to->owner == OWNER_NONE,
229 this->cached_links[from->index][to->index]);
235 * Add information from a given pair of link stat and flow stat to the given
236 * link properties. The shown usage or plan is always the maximum of all link
237 * stats involved.
238 * @param new_cap Capacity of the new link.
239 * @param new_usg Usage of the new link.
240 * @param new_plan Planned flow for the new link.
241 * @param new_shared If the new link is shared.
242 * @param cargo LinkProperties to write the information to.
244 /* static */ void LinkGraphOverlay::AddStats(CargoID new_cargo, uint new_cap, uint new_usg, uint new_plan, uint32_t time, bool new_shared, LinkProperties &cargo)
246 /* multiply the numbers by 32 in order to avoid comparing to 0 too often. */
247 if (cargo.capacity == 0 ||
248 cargo.Usage() * 32 / (cargo.capacity + 1) < std::max(new_usg, new_plan) * 32 / (new_cap + 1)) {
249 cargo.cargo = new_cargo;
250 cargo.capacity = new_cap;
251 cargo.usage = new_usg;
252 cargo.planned = new_plan;
253 cargo.time = time;
255 if (new_shared) cargo.shared = true;
259 * Draw the linkgraph overlay or some part of it, in the area given.
260 * @param dpi Area to be drawn to.
262 void LinkGraphOverlay::Draw(const DrawPixelInfo *dpi)
264 if (this->dirty) {
265 this->RebuildCache();
266 this->dirty = false;
268 this->DrawLinks(dpi);
269 this->DrawStationDots(dpi);
273 * Draw the cached links or part of them into the given area.
274 * @param dpi Area to be drawn to.
276 void LinkGraphOverlay::DrawLinks(const DrawPixelInfo *dpi) const
278 int width = ScaleGUITrad(this->scale);
279 for (const auto &i : this->cached_links) {
280 if (!Station::IsValidID(i.first)) continue;
281 Point pta = this->GetStationMiddle(Station::Get(i.first));
282 for (const auto &j : i.second) {
283 if (!Station::IsValidID(j.first)) continue;
284 Point ptb = this->GetStationMiddle(Station::Get(j.first));
285 if (!this->IsLinkVisible(pta, ptb, dpi, width + 2)) continue;
286 this->DrawContent(pta, ptb, j.second);
292 * Draw one specific link.
293 * @param pta Source of the link.
294 * @param ptb Destination of the link.
295 * @param cargo Properties of the link.
297 void LinkGraphOverlay::DrawContent(Point pta, Point ptb, const LinkProperties &cargo) const
299 uint usage_or_plan = std::min(cargo.capacity * 2 + 1, cargo.Usage());
300 int colour = LinkGraphOverlay::LINK_COLOURS[_settings_client.gui.linkgraph_colours][usage_or_plan * lengthof(LinkGraphOverlay::LINK_COLOURS[0]) / (cargo.capacity * 2 + 2)];
301 int width = ScaleGUITrad(this->scale);
302 int dash = cargo.shared ? width * 4 : 0;
304 /* Move line a bit 90° against its dominant direction to prevent it from
305 * being hidden below the grey line. */
306 int side = _settings_game.vehicle.road_side ? 1 : -1;
307 if (abs(pta.x - ptb.x) < abs(pta.y - ptb.y)) {
308 int offset_x = (pta.y > ptb.y ? 1 : -1) * side * width;
309 GfxDrawLine(pta.x + offset_x, pta.y, ptb.x + offset_x, ptb.y, colour, width, dash);
310 } else {
311 int offset_y = (pta.x < ptb.x ? 1 : -1) * side * width;
312 GfxDrawLine(pta.x, pta.y + offset_y, ptb.x, ptb.y + offset_y, colour, width, dash);
315 GfxDrawLine(pta.x, pta.y, ptb.x, ptb.y, GetColourGradient(COLOUR_GREY, SHADE_DARKEST), width);
319 * Draw dots for stations into the smallmap. The dots' sizes are determined by the amount of
320 * cargo produced there, their colours by the type of cargo produced.
322 void LinkGraphOverlay::DrawStationDots(const DrawPixelInfo *dpi) const
324 int width = ScaleGUITrad(this->scale);
325 for (const auto &i : this->cached_stations) {
326 const Station *st = Station::GetIfValid(i.first);
327 if (st == nullptr) continue;
328 Point pt = this->GetStationMiddle(st);
329 if (!this->IsPointVisible(pt, dpi, 3 * width)) continue;
331 uint r = width * 2 + width * 2 * std::min(200U, i.second) / 200;
333 LinkGraphOverlay::DrawVertex(pt.x, pt.y, r,
334 GetColourGradient(st->owner != OWNER_NONE ?
335 Company::Get(st->owner)->colour : COLOUR_GREY, SHADE_LIGHT),
336 GetColourGradient(COLOUR_GREY, SHADE_DARKEST));
341 * Draw a square symbolizing a producer of cargo.
342 * @param x X coordinate of the middle of the vertex.
343 * @param y Y coordinate of the middle of the vertex.
344 * @param size x and y extent of the vertex.
345 * @param colour Colour with which the vertex will be filled.
346 * @param border_colour Colour for the border of the vertex.
348 /* static */ void LinkGraphOverlay::DrawVertex(int x, int y, int size, int colour, int border_colour)
350 size--;
351 int w1 = size / 2;
352 int w2 = size / 2 + size % 2;
353 int borderwidth = ScaleGUITrad(1);
355 GfxFillRect(x - w1 - borderwidth, y - w1 - borderwidth, x + w2 + borderwidth, y + w2 + borderwidth, border_colour);
356 GfxFillRect(x - w1, y - w1, x + w2, y + w2, colour);
359 bool LinkGraphOverlay::ShowTooltip(Point pt, TooltipCloseCondition close_cond)
361 for (auto i(this->cached_links.crbegin()); i != this->cached_links.crend(); ++i) {
362 if (!Station::IsValidID(i->first)) continue;
363 Point pta = this->GetStationMiddle(Station::Get(i->first));
364 for (auto j(i->second.crbegin()); j != i->second.crend(); ++j) {
365 if (!Station::IsValidID(j->first)) continue;
366 if (i->first == j->first) continue;
368 /* Check the distance from the cursor to the line defined by the two stations. */
369 Point ptb = this->GetStationMiddle(Station::Get(j->first));
370 float dist = std::abs((int64_t)(ptb.x - pta.x) * (int64_t)(pta.y - pt.y) - (int64_t)(pta.x - pt.x) * (int64_t)(ptb.y - pta.y)) /
371 std::sqrt((int64_t)(ptb.x - pta.x) * (int64_t)(ptb.x - pta.x) + (int64_t)(ptb.y - pta.y) * (int64_t)(ptb.y - pta.y));
372 const auto &link = j->second;
373 if (dist <= 4 && link.Usage() > 0 &&
374 pt.x + 2 >= std::min(pta.x, ptb.x) &&
375 pt.x - 2 <= std::max(pta.x, ptb.x) &&
376 pt.y + 2 >= std::min(pta.y, ptb.y) &&
377 pt.y - 2 <= std::max(pta.y, ptb.y)) {
378 static std::string tooltip_extension;
379 tooltip_extension.clear();
380 /* Fill buf with more information if this is a bidirectional link. */
381 uint32_t back_time = 0;
382 auto k = this->cached_links[j->first].find(i->first);
383 if (k != this->cached_links[j->first].end()) {
384 const auto &back = k->second;
385 back_time = back.time;
386 if (back.Usage() > 0) {
387 SetDParam(0, back.cargo);
388 SetDParam(1, back.Usage());
389 SetDParam(2, back.Usage() * 100 / (back.capacity + 1));
390 tooltip_extension = GetString(STR_LINKGRAPH_STATS_TOOLTIP_RETURN_EXTENSION);
393 /* Add information about the travel time if known. */
394 const auto time = link.time ? back_time ? ((link.time + back_time) / 2) : link.time : back_time;
395 if (time > 0) {
396 SetDParam(0, time);
397 AppendStringInPlace(tooltip_extension, STR_LINKGRAPH_STATS_TOOLTIP_TIME_EXTENSION);
399 SetDParam(0, link.cargo);
400 SetDParam(1, link.Usage());
401 SetDParam(2, i->first);
402 SetDParam(3, j->first);
403 SetDParam(4, link.Usage() * 100 / (link.capacity + 1));
404 SetDParamStr(5, tooltip_extension);
405 GuiShowTooltips(this->window,
406 TimerGameEconomy::UsingWallclockUnits() ? STR_LINKGRAPH_STATS_TOOLTIP_MINUTE : STR_LINKGRAPH_STATS_TOOLTIP_MONTH,
407 close_cond, 7);
408 return true;
412 GuiShowTooltips(this->window, STR_NULL, close_cond);
413 return false;
417 * Determine the middle of a station in the current window.
418 * @param st The station we're looking for.
419 * @return Middle point of the station in the current window.
421 Point LinkGraphOverlay::GetStationMiddle(const Station *st) const
423 if (this->window->viewport != nullptr) {
424 return GetViewportStationMiddle(this->window->viewport, st);
425 } else {
426 /* assume this is a smallmap */
427 return GetSmallMapStationMiddle(this->window, st);
432 * Set a new cargo mask and rebuild the cache.
433 * @param cargo_mask New cargo mask.
435 void LinkGraphOverlay::SetCargoMask(CargoTypes cargo_mask)
437 this->cargo_mask = cargo_mask;
438 this->RebuildCache();
439 this->window->GetWidget<NWidgetBase>(this->widget_id)->SetDirty(this->window);
443 * Set a new company mask and rebuild the cache.
444 * @param company_mask New company mask.
446 void LinkGraphOverlay::SetCompanyMask(CompanyMask company_mask)
448 this->company_mask = company_mask;
449 this->RebuildCache();
450 this->window->GetWidget<NWidgetBase>(this->widget_id)->SetDirty(this->window);
453 /** Make a number of rows with buttons for each company for the linkgraph legend window. */
454 std::unique_ptr<NWidgetBase> MakeCompanyButtonRowsLinkGraphGUI()
456 return MakeCompanyButtonRows(WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST, COLOUR_GREY, 3, STR_NULL);
459 std::unique_ptr<NWidgetBase> MakeSaturationLegendLinkGraphGUI()
461 auto panel = std::make_unique<NWidgetVertical>(NC_EQUALSIZE);
462 for (uint i = 0; i < lengthof(LinkGraphOverlay::LINK_COLOURS[0]); ++i) {
463 auto wid = std::make_unique<NWidgetBackground>(WWT_PANEL, COLOUR_DARK_GREEN, i + WID_LGL_SATURATION_FIRST);
464 wid->SetMinimalSize(50, 0);
465 wid->SetMinimalTextLines(1, 0, FS_SMALL);
466 wid->SetFill(1, 1);
467 wid->SetResize(0, 0);
468 panel->Add(std::move(wid));
470 return panel;
473 std::unique_ptr<NWidgetBase> MakeCargoesLegendLinkGraphGUI()
475 uint num_cargo = static_cast<uint>(_sorted_cargo_specs.size());
476 static const uint ENTRIES_PER_COL = 5;
477 auto panel = std::make_unique<NWidgetHorizontal>(NC_EQUALSIZE);
478 std::unique_ptr<NWidgetVertical> col = nullptr;
480 for (uint i = 0; i < num_cargo; ++i) {
481 if (i % ENTRIES_PER_COL == 0) {
482 if (col != nullptr) panel->Add(std::move(col));
483 col = std::make_unique<NWidgetVertical>(NC_EQUALSIZE);
485 auto wid = std::make_unique<NWidgetBackground>(WWT_PANEL, COLOUR_GREY, i + WID_LGL_CARGO_FIRST);
486 wid->SetMinimalSize(25, 0);
487 wid->SetMinimalTextLines(1, 0, FS_SMALL);
488 wid->SetFill(1, 1);
489 wid->SetResize(0, 0);
490 col->Add(std::move(wid));
492 /* Fill up last row */
493 for (uint i = num_cargo; i < Ceil(num_cargo, ENTRIES_PER_COL); ++i) {
494 auto spc = std::make_unique<NWidgetSpacer>(25, 0);
495 spc->SetMinimalTextLines(1, 0, FS_SMALL);
496 spc->SetFill(1, 1);
497 spc->SetResize(0, 0);
498 col->Add(std::move(spc));
500 /* If there are no cargo specs defined, then col won't have been created so don't add it. */
501 if (col != nullptr) panel->Add(std::move(col));
502 return panel;
506 static constexpr NWidgetPart _nested_linkgraph_legend_widgets[] = {
507 NWidget(NWID_HORIZONTAL),
508 NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
509 NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_LGL_CAPTION), SetDataTip(STR_LINKGRAPH_LEGEND_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
510 NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN),
511 NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN),
512 EndContainer(),
513 NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
514 NWidget(NWID_HORIZONTAL), SetPadding(WidgetDimensions::unscaled.framerect), SetPIP(0, WidgetDimensions::unscaled.framerect.Horizontal(), 0),
515 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_LGL_SATURATION),
516 NWidgetFunction(MakeSaturationLegendLinkGraphGUI),
517 EndContainer(),
518 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_LGL_COMPANIES),
519 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
520 NWidgetFunction(MakeCompanyButtonRowsLinkGraphGUI),
521 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_LGL_COMPANIES_ALL), SetDataTip(STR_LINKGRAPH_LEGEND_ALL, STR_NULL),
522 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_LGL_COMPANIES_NONE), SetDataTip(STR_LINKGRAPH_LEGEND_NONE, STR_NULL),
523 EndContainer(),
524 EndContainer(),
525 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_LGL_CARGOES),
526 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
527 NWidgetFunction(MakeCargoesLegendLinkGraphGUI),
528 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_LGL_CARGOES_ALL), SetDataTip(STR_LINKGRAPH_LEGEND_ALL, STR_NULL),
529 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_LGL_CARGOES_NONE), SetDataTip(STR_LINKGRAPH_LEGEND_NONE, STR_NULL),
530 EndContainer(),
531 EndContainer(),
532 EndContainer(),
533 EndContainer()
536 static_assert(WID_LGL_SATURATION_LAST - WID_LGL_SATURATION_FIRST ==
537 lengthof(LinkGraphOverlay::LINK_COLOURS[0]) - 1);
539 static WindowDesc _linkgraph_legend_desc(
540 WDP_AUTO, "toolbar_linkgraph", 0, 0,
541 WC_LINKGRAPH_LEGEND, WC_NONE,
543 _nested_linkgraph_legend_widgets
547 * Open a link graph legend window.
549 void ShowLinkGraphLegend()
551 AllocateWindowDescFront<LinkGraphLegendWindow>(_linkgraph_legend_desc, 0);
554 LinkGraphLegendWindow::LinkGraphLegendWindow(WindowDesc &desc, int window_number) : Window(desc)
556 this->num_cargo = _sorted_cargo_specs.size();
558 this->InitNested(window_number);
559 this->InvalidateData(0);
560 this->SetOverlay(GetMainWindow()->viewport->overlay);
564 * Set the overlay belonging to this menu and import its company/cargo settings.
565 * @param overlay New overlay for this menu.
567 void LinkGraphLegendWindow::SetOverlay(std::shared_ptr<LinkGraphOverlay> overlay)
569 this->overlay = overlay;
570 CompanyMask companies = this->overlay->GetCompanyMask();
571 for (uint c = 0; c < MAX_COMPANIES; c++) {
572 if (!this->IsWidgetDisabled(WID_LGL_COMPANY_FIRST + c)) {
573 this->SetWidgetLoweredState(WID_LGL_COMPANY_FIRST + c, HasBit(companies, c));
576 CargoTypes cargoes = this->overlay->GetCargoMask();
577 for (uint c = 0; c < this->num_cargo; c++) {
578 this->SetWidgetLoweredState(WID_LGL_CARGO_FIRST + c, HasBit(cargoes, _sorted_cargo_specs[c]->Index()));
582 void LinkGraphLegendWindow::UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize)
584 if (IsInsideMM(widget, WID_LGL_SATURATION_FIRST, WID_LGL_SATURATION_LAST + 1)) {
585 StringID str = STR_NULL;
586 if (widget == WID_LGL_SATURATION_FIRST) {
587 str = STR_LINKGRAPH_LEGEND_UNUSED;
588 } else if (widget == WID_LGL_SATURATION_LAST) {
589 str = STR_LINKGRAPH_LEGEND_OVERLOADED;
590 } else if (widget == (WID_LGL_SATURATION_LAST + WID_LGL_SATURATION_FIRST) / 2) {
591 str = STR_LINKGRAPH_LEGEND_SATURATED;
593 if (str != STR_NULL) {
594 Dimension dim = GetStringBoundingBox(str, FS_SMALL);
595 dim.width += padding.width;
596 dim.height += padding.height;
597 size = maxdim(size, dim);
600 if (IsInsideMM(widget, WID_LGL_CARGO_FIRST, WID_LGL_CARGO_LAST + 1)) {
601 const CargoSpec *cargo = _sorted_cargo_specs[widget - WID_LGL_CARGO_FIRST];
602 Dimension dim = GetStringBoundingBox(cargo->abbrev, FS_SMALL);
603 dim.width += padding.width;
604 dim.height += padding.height;
605 size = maxdim(size, dim);
609 void LinkGraphLegendWindow::DrawWidget(const Rect &r, WidgetID widget) const
611 Rect br = r.Shrink(WidgetDimensions::scaled.bevel);
612 if (IsInsideMM(widget, WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST + 1)) {
613 if (this->IsWidgetDisabled(widget)) return;
614 CompanyID cid = (CompanyID)(widget - WID_LGL_COMPANY_FIRST);
615 Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON);
616 DrawCompanyIcon(cid, CenterBounds(br.left, br.right, sprite_size.width), CenterBounds(br.top, br.bottom, sprite_size.height));
618 if (IsInsideMM(widget, WID_LGL_SATURATION_FIRST, WID_LGL_SATURATION_LAST + 1)) {
619 uint8_t colour = LinkGraphOverlay::LINK_COLOURS[_settings_client.gui.linkgraph_colours][widget - WID_LGL_SATURATION_FIRST];
620 GfxFillRect(br, colour);
621 StringID str = STR_NULL;
622 if (widget == WID_LGL_SATURATION_FIRST) {
623 str = STR_LINKGRAPH_LEGEND_UNUSED;
624 } else if (widget == WID_LGL_SATURATION_LAST) {
625 str = STR_LINKGRAPH_LEGEND_OVERLOADED;
626 } else if (widget == (WID_LGL_SATURATION_LAST + WID_LGL_SATURATION_FIRST) / 2) {
627 str = STR_LINKGRAPH_LEGEND_SATURATED;
629 if (str != STR_NULL) {
630 DrawString(br.left, br.right, CenterBounds(br.top, br.bottom, GetCharacterHeight(FS_SMALL)), str, GetContrastColour(colour) | TC_FORCED, SA_HOR_CENTER, false, FS_SMALL);
633 if (IsInsideMM(widget, WID_LGL_CARGO_FIRST, WID_LGL_CARGO_LAST + 1)) {
634 const CargoSpec *cargo = _sorted_cargo_specs[widget - WID_LGL_CARGO_FIRST];
635 GfxFillRect(br, cargo->legend_colour);
636 DrawString(br.left, br.right, CenterBounds(br.top, br.bottom, GetCharacterHeight(FS_SMALL)), cargo->abbrev, GetContrastColour(cargo->legend_colour, 73), SA_HOR_CENTER, false, FS_SMALL);
640 bool LinkGraphLegendWindow::OnTooltip([[maybe_unused]] Point, WidgetID widget, TooltipCloseCondition close_cond)
642 if (IsInsideMM(widget, WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST + 1)) {
643 if (this->IsWidgetDisabled(widget)) {
644 GuiShowTooltips(this, STR_LINKGRAPH_LEGEND_SELECT_COMPANIES, close_cond);
645 } else {
646 SetDParam(0, STR_LINKGRAPH_LEGEND_SELECT_COMPANIES);
647 SetDParam(1, (CompanyID)(widget - WID_LGL_COMPANY_FIRST));
648 GuiShowTooltips(this, STR_LINKGRAPH_LEGEND_COMPANY_TOOLTIP, close_cond, 2);
650 return true;
652 if (IsInsideMM(widget, WID_LGL_CARGO_FIRST, WID_LGL_CARGO_LAST + 1)) {
653 const CargoSpec *cargo = _sorted_cargo_specs[widget - WID_LGL_CARGO_FIRST];
654 GuiShowTooltips(this, cargo->name, close_cond);
655 return true;
657 return false;
661 * Update the overlay with the new company selection.
663 void LinkGraphLegendWindow::UpdateOverlayCompanies()
665 uint32_t mask = 0;
666 for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) {
667 if (this->IsWidgetDisabled(WID_LGL_COMPANY_FIRST + c)) continue;
668 if (!this->IsWidgetLowered(WID_LGL_COMPANY_FIRST + c)) continue;
669 SetBit(mask, c);
671 this->overlay->SetCompanyMask(mask);
675 * Update the overlay with the new cargo selection.
677 void LinkGraphLegendWindow::UpdateOverlayCargoes()
679 CargoTypes mask = 0;
680 for (uint c = 0; c < num_cargo; c++) {
681 if (!this->IsWidgetLowered(WID_LGL_CARGO_FIRST + c)) continue;
682 SetBit(mask, _sorted_cargo_specs[c]->Index());
684 this->overlay->SetCargoMask(mask);
687 void LinkGraphLegendWindow::OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count)
689 /* Check which button is clicked */
690 if (IsInsideMM(widget, WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST + 1)) {
691 if (!this->IsWidgetDisabled(widget)) {
692 this->ToggleWidgetLoweredState(widget);
693 this->UpdateOverlayCompanies();
695 } else if (widget == WID_LGL_COMPANIES_ALL || widget == WID_LGL_COMPANIES_NONE) {
696 for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) {
697 if (this->IsWidgetDisabled(WID_LGL_COMPANY_FIRST + c)) continue;
698 this->SetWidgetLoweredState(WID_LGL_COMPANY_FIRST + c, widget == WID_LGL_COMPANIES_ALL);
700 this->UpdateOverlayCompanies();
701 this->SetDirty();
702 } else if (IsInsideMM(widget, WID_LGL_CARGO_FIRST, WID_LGL_CARGO_LAST + 1)) {
703 this->ToggleWidgetLoweredState(widget);
704 this->UpdateOverlayCargoes();
705 } else if (widget == WID_LGL_CARGOES_ALL || widget == WID_LGL_CARGOES_NONE) {
706 for (uint c = 0; c < this->num_cargo; c++) {
707 this->SetWidgetLoweredState(WID_LGL_CARGO_FIRST + c, widget == WID_LGL_CARGOES_ALL);
709 this->UpdateOverlayCargoes();
711 this->SetDirty();
715 * Invalidate the data of this window if the cargoes or companies have changed.
716 * @param data ignored
717 * @param gui_scope ignored
719 void LinkGraphLegendWindow::OnInvalidateData([[maybe_unused]] int data, [[maybe_unused]] bool gui_scope)
721 if (this->num_cargo != _sorted_cargo_specs.size()) {
722 this->Close();
723 return;
726 /* Disable the companies who are not active */
727 for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) {
728 this->SetWidgetDisabledState(WID_LGL_COMPANY_FIRST + i, !Company::IsValidID(i));