OpenTTD Source 20250312-master-gcdcc6b491d
station_gui.cpp
Go to the documentation of this file.
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 */
7
10#include "stdafx.h"
11#include "debug.h"
12#include "gui.h"
13#include "textbuf_gui.h"
14#include "company_func.h"
15#include "command_func.h"
16#include "vehicle_gui.h"
17#include "cargotype.h"
18#include "station_gui.h"
19#include "strings_func.h"
20#include "string_func.h"
21#include "window_func.h"
22#include "viewport_func.h"
23#include "dropdown_type.h"
25#include "dropdown_func.h"
26#include "station_base.h"
27#include "waypoint_base.h"
28#include "tilehighlight_func.h"
29#include "company_base.h"
30#include "sortlist_type.h"
32#include "vehiclelist.h"
33#include "town.h"
34#include "linkgraph/linkgraph.h"
35#include "zoom_func.h"
36#include "station_cmd.h"
37
39
40#include "table/strings.h"
41
42#include "safeguards.h"
43
45{
46 using StationType = Station;
47
48 static bool IsValidID(StationID id) { return Station::IsValidID(id); }
49 static bool IsValidBaseStation(const BaseStation *st) { return Station::IsExpected(st); }
50 static bool IsAcceptableWaypointTile(TileIndex) { return false; }
51 static constexpr bool IsWaypoint() { return false; }
52};
53
54template <bool ROAD, TileType TILE_TYPE>
56{
57 using StationType = Waypoint;
58
59 static bool IsValidID(StationID id) { return Waypoint::IsValidID(id) && HasBit(Waypoint::Get(id)->waypoint_flags, WPF_ROAD) == ROAD; }
60 static bool IsValidBaseStation(const BaseStation *st) { return Waypoint::IsExpected(st) && HasBit(Waypoint::From(st)->waypoint_flags, WPF_ROAD) == ROAD; }
61 static bool IsAcceptableWaypointTile(TileIndex tile) { return IsTileType(tile, TILE_TYPE); }
62 static constexpr bool IsWaypoint() { return true; }
63};
66
75int DrawStationCoverageAreaText(const Rect &r, StationCoverageType sct, int rad, bool supplies)
76{
77 TileIndex tile = TileVirtXY(_thd.pos.x, _thd.pos.y);
78 CargoTypes cargo_mask = 0;
79 if (_thd.drawstyle == HT_RECT && tile < Map::Size()) {
80 CargoArray cargoes;
81 if (supplies) {
82 cargoes = GetProductionAroundTiles(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE, rad);
83 } else {
84 cargoes = GetAcceptanceAroundTiles(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE, rad);
85 }
86
87 /* Convert cargo counts to a set of cargo bits, and draw the result. */
88 for (CargoType i = 0; i < NUM_CARGO; i++) {
89 switch (sct) {
90 case SCT_PASSENGERS_ONLY: if (!IsCargoInClass(i, CargoClass::Passengers)) continue; break;
92 case SCT_ALL: break;
93 default: NOT_REACHED();
94 }
95 if (cargoes[i] >= (supplies ? 1U : 8U)) SetBit(cargo_mask, i);
96 }
97 }
98 return DrawStringMultiLine(r, GetString(supplies ? STR_STATION_BUILD_SUPPLIES_CARGO : STR_STATION_BUILD_ACCEPTS_CARGO, cargo_mask));
99}
100
105template <typename T>
107{
108 /* With distant join we don't know which station will be selected, so don't show any */
109 if (_ctrl_pressed) {
110 SetViewportCatchmentSpecializedStation<typename T::StationType>(nullptr, true);
111 return;
112 }
113
114 /* Tile area for TileHighlightData */
115 TileArea location(TileVirtXY(_thd.pos.x, _thd.pos.y), _thd.size.x / TILE_SIZE - 1, _thd.size.y / TILE_SIZE - 1);
116
117 /* If the current tile is already a station, then it must be the nearest station. */
118 if (IsTileType(location.tile, MP_STATION) && GetTileOwner(location.tile) == _local_company) {
119 typename T::StationType *st = T::StationType::GetByTile(location.tile);
120 if (st != nullptr && T::IsValidBaseStation(st)) {
121 SetViewportCatchmentSpecializedStation<typename T::StationType>(st, true);
122 return;
123 }
124 }
125
126 /* Extended area by one tile */
127 uint x = TileX(location.tile);
128 uint y = TileY(location.tile);
129
130 /* Waypoints can only be built on existing rail/road tiles, so don't extend area if not highlighting a rail tile. */
131 int max_c = T::IsWaypoint() && !T::IsAcceptableWaypointTile(location.tile) ? 0 : 1;
132 TileArea ta(TileXY(std::max<int>(0, x - max_c), std::max<int>(0, y - max_c)), TileXY(std::min<int>(Map::MaxX(), x + location.w + max_c), std::min<int>(Map::MaxY(), y + location.h + max_c)));
133
134 typename T::StationType *adjacent = nullptr;
135
136 /* Direct loop instead of ForAllStationsAroundTiles as we are not interested in catchment area */
137 for (TileIndex tile : ta) {
138 if (IsTileType(tile, MP_STATION) && GetTileOwner(tile) == _local_company) {
139 typename T::StationType *st = T::StationType::GetByTile(tile);
140 if (st == nullptr || !T::IsValidBaseStation(st)) continue;
141 if (adjacent != nullptr && st != adjacent) {
142 /* Multiple nearby, distant join is required. */
143 adjacent = nullptr;
144 break;
145 }
146 adjacent = st;
147 }
148 }
149 SetViewportCatchmentSpecializedStation<typename T::StationType>(adjacent, true);
150}
151
158{
159 /* Test if ctrl state changed */
160 static bool _last_ctrl_pressed;
161 if (_ctrl_pressed != _last_ctrl_pressed) {
162 _thd.dirty = 0xff;
163 _last_ctrl_pressed = _ctrl_pressed;
164 }
165
166 if (_thd.dirty & 1) {
167 _thd.dirty &= ~1;
168 w->SetDirty();
169
171 FindStationsAroundSelection<StationTypeFilter>();
172 }
173 }
174}
175
176template <typename T>
177void CheckRedrawWaypointCoverage()
178{
179 /* Test if ctrl state changed */
180 static bool _last_ctrl_pressed;
181 if (_ctrl_pressed != _last_ctrl_pressed) {
182 _thd.dirty = 0xff;
183 _last_ctrl_pressed = _ctrl_pressed;
184 }
185
186 if (_thd.dirty & 1) {
187 _thd.dirty &= ~1;
188
189 if (_thd.drawstyle == HT_RECT) {
190 FindStationsAroundSelection<T>();
191 }
192 }
193}
194
195void CheckRedrawRailWaypointCoverage(const Window *)
196{
197 CheckRedrawWaypointCoverage<RailWaypointTypeFilter>();
198}
199
200void CheckRedrawRoadWaypointCoverage(const Window *)
201{
202 CheckRedrawWaypointCoverage<RoadWaypointTypeFilter>();
203}
204
217static void StationsWndShowStationRating(int left, int right, int y, CargoType type, uint amount, uint8_t rating)
218{
219 static const uint units_full = 576;
220 static const uint rating_full = 224;
221
222 const CargoSpec *cs = CargoSpec::Get(type);
223 if (!cs->IsValid()) return;
224
225 int padding = ScaleGUITrad(1);
226 int width = right - left;
227 int colour = cs->rating_colour;
228 TextColour tc = GetContrastColour(colour);
229 uint w = std::min(amount + 5, units_full) * width / units_full;
230
231 int height = GetCharacterHeight(FS_SMALL) + padding - 1;
232
233 if (amount > 30) {
234 /* Draw total cargo (limited) on station */
235 GfxFillRect(left, y, left + w - 1, y + height, colour);
236 } else {
237 /* Draw a (scaled) one pixel-wide bar of additional cargo meter, useful
238 * for stations with only a small amount (<=30) */
239 uint rest = ScaleGUITrad(amount) / 5;
240 if (rest != 0) {
241 GfxFillRect(left, y + height - rest, left + padding - 1, y + height, colour);
242 }
243 }
244
245 DrawString(left + padding, right, y, cs->abbrev, tc, SA_CENTER, false, FS_SMALL);
246
247 /* Draw green/red ratings bar (fits under the waiting bar) */
248 y += height + padding + 1;
249 GfxFillRect(left + padding, y, right - padding - 1, y + padding - 1, PC_RED);
250 w = std::min<uint>(rating, rating_full) * (width - padding - padding) / rating_full;
251 if (w != 0) GfxFillRect(left + padding, y, left + w - 1, y + padding - 1, PC_GREEN);
252}
253
255
260{
261protected:
262 /* Runtime saved values */
263 struct FilterState {
264 Listing last_sorting;
267 CargoTypes cargoes;
268 };
269
270 static inline FilterState initial_state = {
271 {false, 0},
273 true,
274 ALL_CARGOTYPES,
275 };
276
277 /* Constants for sorting stations */
278 static inline const StringID sorter_names[] = {
285 };
286 static const std::initializer_list<GUIStationList::SortFunction * const> sorter_funcs;
287
288 FilterState filter{};
289 GUIStationList stations{filter.cargoes};
290 Scrollbar *vscroll = nullptr;
291 uint rating_width = 0;
292 bool filter_expanded = false;
293 std::array<uint16_t, NUM_CARGO> stations_per_cargo_type{};
295
302 {
303 if (!this->stations.NeedRebuild()) return;
304
305 Debug(misc, 3, "Building station list for company {}", owner);
306
307 this->stations.clear();
308 this->stations_per_cargo_type.fill(0);
309 this->stations_per_cargo_type_no_rating = 0;
310
311 for (const Station *st : Station::Iterate()) {
312 if (this->filter.facilities.Any(st->facilities)) { // only stations with selected facilities
313 if (st->owner == owner || (st->owner == OWNER_NONE && HasStationInUse(st->index, true, owner))) {
314 bool has_rating = false;
315 /* Add to the station/cargo counts. */
316 for (CargoType j = 0; j < NUM_CARGO; j++) {
317 if (st->goods[j].HasRating()) this->stations_per_cargo_type[j]++;
318 }
319 for (CargoType j = 0; j < NUM_CARGO; j++) {
320 if (st->goods[j].HasRating()) {
321 has_rating = true;
322 if (HasBit(this->filter.cargoes, j)) {
323 this->stations.push_back(st);
324 break;
325 }
326 }
327 }
328 /* Stations with no cargo rating. */
329 if (!has_rating) {
330 if (this->filter.include_no_rating) this->stations.push_back(st);
331 this->stations_per_cargo_type_no_rating++;
332 }
333 }
334 }
335 }
336
337 this->stations.RebuildDone();
338
339 this->vscroll->SetCount(this->stations.size()); // Update the scrollbar
340 }
341
343 static bool StationNameSorter(const Station * const &a, const Station * const &b, const CargoTypes &)
344 {
345 int r = StrNaturalCompare(a->GetCachedName(), b->GetCachedName()); // Sort by name (natural sorting).
346 if (r == 0) return a->index < b->index;
347 return r < 0;
348 }
349
351 static bool StationTypeSorter(const Station * const &a, const Station * const &b, const CargoTypes &)
352 {
353 return a->facilities < b->facilities;
354 }
355
357 static bool StationWaitingTotalSorter(const Station * const &a, const Station * const &b, const CargoTypes &cargo_filter)
358 {
359 int diff = 0;
360
362 diff += (a->goods[j].HasData() ? a->goods[j].GetData().cargo.TotalCount() : 0) - (b->goods[j].HasData() ? b->goods[j].GetData().cargo.TotalCount() : 0);
363 }
364
365 return diff < 0;
366 }
367
369 static bool StationWaitingAvailableSorter(const Station * const &a, const Station * const &b, const CargoTypes &cargo_filter)
370 {
371 int diff = 0;
372
374 diff += (a->goods[j].HasData() ? a->goods[j].GetData().cargo.AvailableCount() : 0) - (b->goods[j].HasData() ? b->goods[j].GetData().cargo.AvailableCount() : 0);
375 }
376
377 return diff < 0;
378 }
379
381 static bool StationRatingMaxSorter(const Station * const &a, const Station * const &b, const CargoTypes &cargo_filter)
382 {
383 uint8_t maxr1 = 0;
384 uint8_t maxr2 = 0;
385
387 if (a->goods[j].HasRating()) maxr1 = std::max(maxr1, a->goods[j].rating);
388 if (b->goods[j].HasRating()) maxr2 = std::max(maxr2, b->goods[j].rating);
389 }
390
391 return maxr1 < maxr2;
392 }
393
395 static bool StationRatingMinSorter(const Station * const &a, const Station * const &b, const CargoTypes &cargo_filter)
396 {
397 uint8_t minr1 = 255;
398 uint8_t minr2 = 255;
399
401 if (a->goods[j].HasRating()) minr1 = std::min(minr1, a->goods[j].rating);
402 if (b->goods[j].HasRating()) minr2 = std::min(minr2, b->goods[j].rating);
403 }
404
405 return minr1 > minr2;
406 }
407
410 {
411 if (!this->stations.Sort()) return;
412
413 /* Set the modified widget dirty */
415 }
416
417public:
419 {
420 /* Load initial filter state. */
421 this->filter = CompanyStationsWindow::initial_state;
422 if (this->filter.cargoes == ALL_CARGOTYPES) this->filter.cargoes = _cargo_mask;
423
424 this->stations.SetListing(this->filter.last_sorting);
425 this->stations.SetSortFuncs(CompanyStationsWindow::sorter_funcs);
426 this->stations.ForceRebuild();
427 this->stations.NeedResort();
428 this->SortStationsList();
429
430 this->CreateNestedTree();
431 this->vscroll = this->GetScrollbar(WID_STL_SCROLLBAR);
432 this->FinishInitNested(window_number);
433 this->owner = this->window_number;
434
435 if (this->filter.cargoes == ALL_CARGOTYPES) this->filter.cargoes = _cargo_mask;
436
437 for (uint i = 0; i < 5; i++) {
438 if (HasBit(this->filter.facilities.base(), i)) this->LowerWidget(i + WID_STL_TRAIN);
439 }
440
441 this->GetWidget<NWidgetCore>(WID_STL_SORTDROPBTN)->SetString(CompanyStationsWindow::sorter_names[this->stations.SortType()]);
442 }
443
445 {
446 /* Save filter state. */
447 this->filter.last_sorting = this->stations.GetListing();
448 CompanyStationsWindow::initial_state = this->filter;
449 }
450
452 {
453 switch (widget) {
454 case WID_STL_SORTBY: {
456 d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
457 d.height += padding.height;
458 size = maxdim(size, d);
459 break;
460 }
461
462 case WID_STL_SORTDROPBTN: {
463 Dimension d = GetStringListBoundingBox(CompanyStationsWindow::sorter_names);
464 d.width += padding.width;
465 d.height += padding.height;
466 size = maxdim(size, d);
467 break;
468 }
469
470 case WID_STL_LIST:
472 size.height = padding.height + 5 * resize.height;
473
474 /* Determine appropriate width for mini station rating graph */
475 this->rating_width = 0;
476 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
477 this->rating_width = std::max(this->rating_width, GetStringBoundingBox(cs->abbrev, FS_SMALL).width);
478 }
479 /* Approximately match original 16 pixel wide rating bars by multiplying string width by 1.6 */
480 this->rating_width = this->rating_width * 16 / 10;
481 break;
482 }
483 }
484
485 void OnPaint() override
486 {
487 this->BuildStationsList(this->window_number);
488 this->SortStationsList();
489
490 this->DrawWidgets();
491 }
492
493 void DrawWidget(const Rect &r, WidgetID widget) const override
494 {
495 switch (widget) {
496 case WID_STL_SORTBY:
497 /* draw arrow pointing up/down for ascending/descending sorting */
499 break;
500
501 case WID_STL_LIST: {
502 bool rtl = _current_text_dir == TD_RTL;
503 Rect tr = r.Shrink(WidgetDimensions::scaled.framerect);
504 uint line_height = this->GetWidget<NWidgetBase>(widget)->resize_y;
505 /* Spacing between station name and first rating graph. */
507 /* Spacing between additional rating graphs. */
509
510 auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->stations);
511 for (auto it = first; it != last; ++it) {
512 const Station *st = *it;
513 assert(st->xy != INVALID_TILE);
514
515 /* Do not do the complex check HasStationInUse here, it may be even false
516 * when the order had been removed and the station list hasn't been removed yet */
517 assert(st->owner == owner || st->owner == OWNER_NONE);
518
519 int x = DrawString(tr.left, tr.right, tr.top + (line_height - GetCharacterHeight(FS_NORMAL)) / 2, GetString(STR_STATION_LIST_STATION, st->index, st->facilities));
520 x += rtl ? -text_spacing : text_spacing;
521
522 /* show cargo waiting and station ratings */
523 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
524 CargoType cargo_type = cs->Index();
525 if (st->goods[cargo_type].HasRating()) {
526 /* For RTL we work in exactly the opposite direction. So
527 * decrement the space needed first, then draw to the left
528 * instead of drawing to the left and then incrementing
529 * the space. */
530 if (rtl) {
531 x -= rating_width + rating_spacing;
532 if (x < tr.left) break;
533 }
534 StationsWndShowStationRating(x, x + rating_width, tr.top, cargo_type, st->goods[cargo_type].HasData() ? st->goods[cargo_type].GetData().cargo.TotalCount() : 0, st->goods[cargo_type].rating);
535 if (!rtl) {
536 x += rating_width + rating_spacing;
537 if (x > tr.right) break;
538 }
539 }
540 }
541 tr.top += line_height;
542 }
543
544 if (this->vscroll->GetCount() == 0) { // company has no stations
545 DrawString(tr.left, tr.right, tr.top + (line_height - GetCharacterHeight(FS_NORMAL)) / 2, STR_STATION_LIST_NONE);
546 return;
547 }
548 break;
549 }
550 }
551 }
552
553 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
554 {
555 if (widget == WID_STL_CAPTION) {
556 return GetString(STR_STATION_LIST_CAPTION, this->window_number, this->vscroll->GetCount());
557 }
558
559 if (widget == WID_STL_CARGODROPDOWN) {
562 if (CountBits(this->filter.cargoes) == 1 && !this->filter.include_no_rating) return GetString(CargoSpec::Get(FindFirstBit(this->filter.cargoes))->name);
564 }
565
566 return this->Window::GetWidgetString(widget, stringid);
567 }
568
569 DropDownList BuildCargoDropDownList(bool expanded) const
570 {
571 /* Define a custom item consisting of check mark, count string, icon and name string. */
573
574 DropDownList list;
575 list.push_back(MakeDropDownListStringItem(STR_STATION_LIST_CARGO_FILTER_SELECT_ALL, CargoFilterCriteria::CF_SELECT_ALL));
576 list.push_back(MakeDropDownListDividerItem());
577
578 bool any_hidden = false;
579
581 if (count == 0 && !expanded) {
582 any_hidden = true;
583 } else {
584 list.push_back(std::make_unique<DropDownString<DropDownListCheckedItem, FS_SMALL, true>>(fmt::format("{}", count), 0, this->filter.include_no_rating, GetString(STR_STATION_LIST_CARGO_FILTER_NO_RATING), CargoFilterCriteria::CF_NO_RATING, false, count == 0));
585 }
586
588 for (const CargoSpec *cs : _sorted_cargo_specs) {
589 count = this->stations_per_cargo_type[cs->Index()];
590 if (count == 0 && !expanded) {
591 any_hidden = true;
592 } else {
593 list.push_back(std::make_unique<DropDownListCargoItem>(HasBit(this->filter.cargoes, cs->Index()), fmt::format("{}", count), d, cs->GetCargoIcon(), PAL_NONE, GetString(cs->name), cs->Index(), false, count == 0));
594 }
595 }
596
597 if (!expanded && any_hidden) {
598 if (list.size() > 2) list.push_back(MakeDropDownListDividerItem());
599 list.push_back(MakeDropDownListStringItem(STR_STATION_LIST_CARGO_FILTER_EXPAND, CargoFilterCriteria::CF_EXPAND_LIST));
600 }
601
602 return list;
603 }
604
605 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
606 {
607 switch (widget) {
608 case WID_STL_LIST: {
609 auto it = this->vscroll->GetScrolledItemFromWidget(this->stations, pt.y, this, WID_STL_LIST, WidgetDimensions::scaled.framerect.top);
610 if (it == this->stations.end()) return; // click out of list bound
611
612 const Station *st = *it;
613 /* do not check HasStationInUse - it is slow and may be invalid */
614 assert(st->owner == this->window_number || st->owner == OWNER_NONE);
615
616 if (_ctrl_pressed) {
618 } else {
620 }
621 break;
622 }
623
624 case WID_STL_TRAIN:
625 case WID_STL_TRUCK:
626 case WID_STL_BUS:
627 case WID_STL_AIRPLANE:
628 case WID_STL_SHIP:
629 if (_ctrl_pressed) {
630 this->filter.facilities.Flip(static_cast<StationFacility>(widget - WID_STL_TRAIN));
631 this->ToggleWidgetLoweredState(widget);
632 } else {
633 for (uint i : SetBitIterator(this->filter.facilities.base())) {
634 this->RaiseWidget(i + WID_STL_TRAIN);
635 }
636 this->filter.facilities = {};
637 this->filter.facilities.Set(static_cast<StationFacility>(widget - WID_STL_TRAIN));
638 this->LowerWidget(widget);
639 }
640 this->stations.ForceRebuild();
641 this->SetDirty();
642 break;
643
644 case WID_STL_FACILALL:
645 for (WidgetID i = WID_STL_TRAIN; i <= WID_STL_SHIP; i++) {
646 this->LowerWidget(i);
647 }
648
650 this->stations.ForceRebuild();
651 this->SetDirty();
652 break;
653
654 case WID_STL_SORTBY: // flip sorting method asc/desc
655 this->stations.ToggleSortOrder();
656 this->SetDirty();
657 break;
658
659 case WID_STL_SORTDROPBTN: // select sorting criteria dropdown menu
660 ShowDropDownMenu(this, CompanyStationsWindow::sorter_names, this->stations.SortType(), WID_STL_SORTDROPBTN, 0, 0);
661 break;
662
664 this->filter_expanded = false;
665 ShowDropDownList(this, this->BuildCargoDropDownList(this->filter_expanded), -1, widget, 0, false, true);
666 break;
667 }
668 }
669
670 void OnDropdownSelect(int widget, int index) override
671 {
672 if (widget == WID_STL_SORTDROPBTN) {
673 if (this->stations.SortType() != index) {
674 this->stations.SetSortType(index);
675
676 /* Display the current sort variant */
677 this->GetWidget<NWidgetCore>(WID_STL_SORTDROPBTN)->SetString(CompanyStationsWindow::sorter_names[this->stations.SortType()]);
678
679 this->SetDirty();
680 }
681 }
682
683 if (widget == WID_STL_CARGODROPDOWN) {
684 FilterState oldstate = this->filter;
685
686 if (index >= 0 && index < NUM_CARGO) {
687 if (_ctrl_pressed) {
688 ToggleBit(this->filter.cargoes, index);
689 } else {
690 this->filter.cargoes = 1ULL << index;
691 this->filter.include_no_rating = false;
692 }
693 } else if (index == CargoFilterCriteria::CF_NO_RATING) {
694 if (_ctrl_pressed) {
695 this->filter.include_no_rating = !this->filter.include_no_rating;
696 } else {
697 this->filter.include_no_rating = true;
698 this->filter.cargoes = 0;
699 }
700 } else if (index == CargoFilterCriteria::CF_SELECT_ALL) {
701 this->filter.cargoes = _cargo_mask;
702 this->filter.include_no_rating = true;
703 } else if (index == CargoFilterCriteria::CF_EXPAND_LIST) {
704 this->filter_expanded = true;
705 ReplaceDropDownList(this, this->BuildCargoDropDownList(this->filter_expanded));
706 return;
707 }
708
709 if (oldstate.cargoes != this->filter.cargoes || oldstate.include_no_rating != this->filter.include_no_rating) {
710 this->stations.ForceRebuild();
711 this->SetDirty();
712
713 /* Only refresh the list if it's changed. */
714 if (_ctrl_pressed) ReplaceDropDownList(this, this->BuildCargoDropDownList(this->filter_expanded));
715 }
716
717 /* Always close the list if ctrl is not pressed. */
719 }
720 }
721
722 void OnGameTick() override
723 {
724 if (this->stations.NeedResort()) {
725 Debug(misc, 3, "Periodic rebuild station list company {}", static_cast<int>(this->window_number));
726 this->SetDirty();
727 }
728 }
729
730 void OnResize() override
731 {
732 this->vscroll->SetCapacityFromWidget(this, WID_STL_LIST, WidgetDimensions::scaled.framerect.Vertical());
733 }
734
740 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
741 {
742 if (data == 0) {
743 /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
744 this->stations.ForceRebuild();
745 } else {
746 this->stations.ForceResort();
747 }
748 }
749};
750
751/* Available station sorting functions */
752const std::initializer_list<GUIStationList::SortFunction * const> CompanyStationsWindow::sorter_funcs = {
753 &StationNameSorter,
754 &StationTypeSorter,
755 &StationWaitingTotalSorter,
756 &StationWaitingAvailableSorter,
757 &StationRatingMaxSorter,
758 &StationRatingMinSorter
759};
760
761static constexpr NWidgetPart _nested_company_stations_widgets[] = {
763 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
764 NWidget(WWT_CAPTION, COLOUR_GREY, WID_STL_CAPTION),
765 NWidget(WWT_SHADEBOX, COLOUR_GREY),
766 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
767 NWidget(WWT_STICKYBOX, COLOUR_GREY),
768 EndContainer(),
770 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_STL_TRAIN), SetAspect(WidgetDimensions::ASPECT_VEHICLE_ICON), SetStringTip(STR_TRAIN, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE_TOOLTIP), SetFill(0, 1),
771 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_STL_TRUCK), SetAspect(WidgetDimensions::ASPECT_VEHICLE_ICON), SetStringTip(STR_LORRY, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE_TOOLTIP), SetFill(0, 1),
772 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_STL_BUS), SetAspect(WidgetDimensions::ASPECT_VEHICLE_ICON), SetStringTip(STR_BUS, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE_TOOLTIP), SetFill(0, 1),
773 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_STL_SHIP), SetAspect(WidgetDimensions::ASPECT_VEHICLE_ICON), SetStringTip(STR_SHIP, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE_TOOLTIP), SetFill(0, 1),
774 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_STL_AIRPLANE), SetAspect(WidgetDimensions::ASPECT_VEHICLE_ICON), SetStringTip(STR_PLANE, STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE_TOOLTIP), SetFill(0, 1),
775 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_STL_FACILALL), SetAspect(WidgetDimensions::ASPECT_VEHICLE_ICON), SetStringTip(STR_ABBREV_ALL, STR_STATION_LIST_SELECT_ALL_FACILITIES_TOOLTIP), SetTextStyle(TC_BLACK, FS_SMALL), SetFill(0, 1),
776 NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(5, 0), SetFill(0, 1), EndContainer(),
777 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_STL_CARGODROPDOWN), SetFill(1, 0), SetToolTip(STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE_TOOLTIP),
778 NWidget(WWT_PANEL, COLOUR_GREY), SetResize(1, 0), SetFill(1, 1), EndContainer(),
779 EndContainer(),
781 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_STL_SORTBY), SetMinimalSize(81, 12), SetStringTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
782 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_STL_SORTDROPBTN), SetMinimalSize(163, 12), SetStringTip(STR_SORT_BY_NAME, STR_TOOLTIP_SORT_CRITERIA), // widget_data gets overwritten.
783 NWidget(WWT_PANEL, COLOUR_GREY), SetResize(1, 0), SetFill(1, 1), EndContainer(),
784 EndContainer(),
786 NWidget(WWT_PANEL, COLOUR_GREY, WID_STL_LIST), SetMinimalSize(346, 125), SetResize(1, 10), SetToolTip(STR_STATION_LIST_TOOLTIP), SetScrollbar(WID_STL_SCROLLBAR), EndContainer(),
789 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
790 EndContainer(),
791 EndContainer(),
792};
793
794static WindowDesc _company_stations_desc(
795 WDP_AUTO, "list_stations", 358, 162,
797 {},
798 _nested_company_stations_widgets
799);
800
807{
808 if (!Company::IsValidID(company)) return;
809
810 AllocateWindowDescFront<CompanyStationsWindow>(_company_stations_desc, company);
811}
812
813static constexpr NWidgetPart _nested_station_view_widgets[] = {
815 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
816 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SV_RENAME), SetAspect(WidgetDimensions::ASPECT_RENAME), SetSpriteTip(SPR_RENAME, STR_STATION_VIEW_RENAME_TOOLTIP),
817 NWidget(WWT_CAPTION, COLOUR_GREY, WID_SV_CAPTION),
818 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SV_LOCATION), SetAspect(WidgetDimensions::ASPECT_LOCATION), SetSpriteTip(SPR_GOTO_LOCATION, STR_STATION_VIEW_CENTER_TOOLTIP),
819 NWidget(WWT_SHADEBOX, COLOUR_GREY),
820 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
821 NWidget(WWT_STICKYBOX, COLOUR_GREY),
822 EndContainer(),
824 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SV_GROUP), SetMinimalSize(81, 12), SetFill(1, 1), SetStringTip(STR_STATION_VIEW_GROUP),
825 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_SV_GROUP_BY), SetMinimalSize(168, 12), SetResize(1, 0), SetFill(0, 1), SetToolTip(STR_TOOLTIP_GROUP_ORDER),
826 EndContainer(),
828 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_SORT_ORDER), SetMinimalSize(81, 12), SetFill(1, 1), SetStringTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
829 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_SV_SORT_BY), SetMinimalSize(168, 12), SetResize(1, 0), SetFill(0, 1), SetToolTip(STR_TOOLTIP_SORT_CRITERIA),
830 EndContainer(),
834 EndContainer(),
837 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_ACCEPTS_RATINGS), SetMinimalSize(46, 12), SetResize(1, 0), SetFill(1, 1),
838 SetStringTip(STR_STATION_VIEW_RATINGS_BUTTON, STR_STATION_VIEW_RATINGS_TOOLTIP),
839 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SV_CLOSE_AIRPORT), SetMinimalSize(45, 12), SetResize(1, 0), SetFill(1, 1),
840 SetStringTip(STR_STATION_VIEW_CLOSE_AIRPORT, STR_STATION_VIEW_CLOSE_AIRPORT_TOOLTIP),
841 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SV_CATCHMENT), SetMinimalSize(45, 12), SetResize(1, 0), SetFill(1, 1), SetStringTip(STR_BUTTON_CATCHMENT, STR_TOOLTIP_CATCHMENT),
842 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_TRAINS), SetAspect(WidgetDimensions::ASPECT_VEHICLE_ICON), SetFill(0, 1), SetStringTip(STR_TRAIN, STR_STATION_VIEW_SCHEDULED_TRAINS_TOOLTIP),
843 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_ROADVEHS), SetAspect(WidgetDimensions::ASPECT_VEHICLE_ICON), SetFill(0, 1), SetStringTip(STR_LORRY, STR_STATION_VIEW_SCHEDULED_ROAD_VEHICLES_TOOLTIP),
844 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_SHIPS), SetAspect(WidgetDimensions::ASPECT_VEHICLE_ICON), SetFill(0, 1), SetStringTip(STR_SHIP, STR_STATION_VIEW_SCHEDULED_SHIPS_TOOLTIP),
845 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_PLANES), SetAspect(WidgetDimensions::ASPECT_VEHICLE_ICON), SetFill(0, 1), SetStringTip(STR_PLANE, STR_STATION_VIEW_SCHEDULED_AIRCRAFT_TOOLTIP),
846 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
847 EndContainer(),
848};
849
859static void DrawCargoIcons(CargoType i, uint waiting, int left, int right, int y)
860{
861 int width = ScaleSpriteTrad(10);
862 uint num = std::min<uint>((waiting + (width / 2)) / width, (right - left) / width); // maximum is width / 10 icons so it won't overflow
863 if (num == 0) return;
864
865 SpriteID sprite = CargoSpec::Get(i)->GetCargoIcon();
866
867 int x = _current_text_dir == TD_RTL ? left : right - num * width;
868 do {
869 DrawSprite(sprite, PAL_NONE, x, y);
870 x += width;
871 } while (--num);
872}
873
874enum SortOrder : uint8_t {
875 SO_DESCENDING,
876 SO_ASCENDING
877};
878
879class CargoDataEntry;
880
881enum class CargoSortType : uint8_t {
882 AsGrouping,
883 Count,
885 StationID,
886 CargoType,
887};
888
890public:
891 CargoSorter(CargoSortType t = CargoSortType::StationID, SortOrder o = SO_ASCENDING) : type(t), order(o) {}
892 CargoSortType GetSortType() {return this->type;}
893 bool operator()(const CargoDataEntry *cd1, const CargoDataEntry *cd2) const;
894
895private:
896 CargoSortType type;
897 SortOrder order;
898
899 template <class Tid>
900 bool SortId(Tid st1, Tid st2) const;
901 bool SortCount(const CargoDataEntry *cd1, const CargoDataEntry *cd2) const;
902 bool SortStation (StationID st1, StationID st2) const;
903};
904
905typedef std::set<CargoDataEntry *, CargoSorter> CargoDataSet;
906
913public:
916
923 {
924 return this->InsertOrRetrieve<StationID>(station);
925 }
926
933 {
934 return this->InsertOrRetrieve<CargoType>(cargo);
935 }
936
937 void Update(uint count);
938
944 {
946 this->Remove(&t);
947 }
948
954 {
956 this->Remove(&t);
957 }
958
965 {
967 return this->Retrieve(this->children->find(&t));
968 }
969
976 {
978 return this->Retrieve(this->children->find(&t));
979 }
980
981 void Resort(CargoSortType type, SortOrder order);
982
986 StationID GetStation() const { return this->station; }
987
991 CargoType GetCargo() const { return this->cargo; }
992
996 uint GetCount() const { return this->count; }
997
1001 CargoDataEntry *GetParent() const { return this->parent; }
1002
1006 uint GetNumChildren() const { return this->num_children; }
1007
1011 CargoDataSet::iterator Begin() const { return this->children->begin(); }
1012
1016 CargoDataSet::iterator End() const { return this->children->end(); }
1017
1021 bool HasTransfers() const { return this->transfers; }
1022
1026 void SetTransfers(bool value) { this->transfers = value; }
1027
1028 void Clear();
1029private:
1030
1031 CargoDataEntry(StationID st, uint c, CargoDataEntry *p);
1032 CargoDataEntry(CargoType car, uint c, CargoDataEntry *p);
1035
1036 CargoDataEntry *Retrieve(CargoDataSet::iterator i) const;
1037
1038 template <class Tid>
1040
1041 void Remove(CargoDataEntry *comp);
1042 void IncrementSize();
1043
1045 const union {
1047 struct {
1050 };
1051 };
1053 uint count;
1054 CargoDataSet *children;
1055};
1056
1057CargoDataEntry::CargoDataEntry() :
1058 parent(nullptr),
1059 station(StationID::Invalid()),
1060 num_children(0),
1061 count(0),
1062 children(new CargoDataSet(CargoSorter(CargoSortType::CargoType)))
1063{}
1064
1065CargoDataEntry::CargoDataEntry(CargoType cargo, uint count, CargoDataEntry *parent) :
1066 parent(parent),
1067 cargo(cargo),
1068 num_children(0),
1069 count(count),
1070 children(new CargoDataSet)
1071{}
1072
1073CargoDataEntry::CargoDataEntry(StationID station, uint count, CargoDataEntry *parent) :
1074 parent(parent),
1075 station(station),
1076 num_children(0),
1077 count(count),
1078 children(new CargoDataSet)
1079{}
1080
1081CargoDataEntry::CargoDataEntry(StationID station) :
1082 parent(nullptr),
1083 station(station),
1084 num_children(0),
1085 count(0),
1086 children(nullptr)
1087{}
1088
1089CargoDataEntry::CargoDataEntry(CargoType cargo) :
1090 parent(nullptr),
1091 cargo(cargo),
1092 num_children(0),
1093 count(0),
1094 children(nullptr)
1095{}
1096
1097CargoDataEntry::~CargoDataEntry()
1098{
1099 this->Clear();
1100 delete this->children;
1101}
1102
1107{
1108 if (this->children != nullptr) {
1109 for (auto &it : *this->children) {
1110 assert(it != this);
1111 delete it;
1112 }
1113 this->children->clear();
1114 }
1115 if (this->parent != nullptr) this->parent->count -= this->count;
1116 this->count = 0;
1117 this->num_children = 0;
1118}
1119
1127{
1128 CargoDataSet::iterator i = this->children->find(child);
1129 if (i != this->children->end()) {
1130 delete *i;
1131 this->children->erase(i);
1132 }
1133}
1134
1141template <class Tid>
1143{
1144 CargoDataEntry tmp(child_id);
1145 CargoDataSet::iterator i = this->children->find(&tmp);
1146 if (i == this->children->end()) {
1147 IncrementSize();
1148 return *(this->children->insert(new CargoDataEntry(child_id, 0, this)).first);
1149 } else {
1150 CargoDataEntry *ret = *i;
1151 assert(this->children->value_comp().GetSortType() != CargoSortType::Count);
1152 return ret;
1153 }
1154}
1155
1162{
1163 this->count += count;
1164 if (this->parent != nullptr) this->parent->Update(count);
1165}
1166
1171{
1172 ++this->num_children;
1173 if (this->parent != nullptr) this->parent->IncrementSize();
1174}
1175
1176void CargoDataEntry::Resort(CargoSortType type, SortOrder order)
1177{
1178 CargoDataSet *new_subs = new CargoDataSet(this->children->begin(), this->children->end(), CargoSorter(type, order));
1179 delete this->children;
1180 this->children = new_subs;
1181}
1182
1183CargoDataEntry *CargoDataEntry::Retrieve(CargoDataSet::iterator i) const
1184{
1185 if (i == this->children->end()) {
1186 return nullptr;
1187 } else {
1188 assert(this->children->value_comp().GetSortType() != CargoSortType::Count);
1189 return *i;
1190 }
1191}
1192
1193bool CargoSorter::operator()(const CargoDataEntry *cd1, const CargoDataEntry *cd2) const
1194{
1195 switch (this->type) {
1197 return this->SortId<StationID>(cd1->GetStation(), cd2->GetStation());
1199 return this->SortId<CargoType>(cd1->GetCargo(), cd2->GetCargo());
1201 return this->SortCount(cd1, cd2);
1203 return this->SortStation(cd1->GetStation(), cd2->GetStation());
1204 default:
1205 NOT_REACHED();
1206 }
1207}
1208
1209template <class Tid>
1210bool CargoSorter::SortId(Tid st1, Tid st2) const
1211{
1212 return (this->order == SO_ASCENDING) ? st1 < st2 : st2 < st1;
1213}
1214
1215bool CargoSorter::SortCount(const CargoDataEntry *cd1, const CargoDataEntry *cd2) const
1216{
1217 uint c1 = cd1->GetCount();
1218 uint c2 = cd2->GetCount();
1219 if (c1 == c2) {
1220 return this->SortStation(cd1->GetStation(), cd2->GetStation());
1221 } else if (this->order == SO_ASCENDING) {
1222 return c1 < c2;
1223 } else {
1224 return c2 < c1;
1225 }
1226}
1227
1228bool CargoSorter::SortStation(StationID st1, StationID st2) const
1229{
1230 if (!Station::IsValidID(st1)) {
1231 return Station::IsValidID(st2) ? this->order == SO_ASCENDING : this->SortId(st1, st2);
1232 } else if (!Station::IsValidID(st2)) {
1233 return order == SO_DESCENDING;
1234 }
1235
1236 int res = StrNaturalCompare(Station::Get(st1)->GetCachedName(), Station::Get(st2)->GetCachedName()); // Sort by name (natural sorting).
1237 if (res == 0) {
1238 return this->SortId(st1, st2);
1239 } else {
1240 return (this->order == SO_ASCENDING) ? res < 0 : res > 0;
1241 }
1242}
1243
1247struct StationViewWindow : public Window {
1271
1272 typedef std::vector<RowDisplay> CargoDataVector;
1273
1274 static const int NUM_COLUMNS = 4;
1275
1280 INV_FLOWS = 0x100,
1281 INV_CARGO = 0x200
1283
1293
1301
1305 Scrollbar *vscroll = nullptr;
1306
1307 /* Height of the #WID_SV_ACCEPT_RATING_LIST widget for different views. */
1308 static constexpr uint RATING_LINES = 13;
1309 static constexpr uint ACCEPTS_LINES = 3;
1310
1327
1334 std::array<CargoSortType, NUM_COLUMNS> sortings{};
1335
1337 std::array<SortOrder, NUM_COLUMNS> sort_orders{};
1338
1342 std::array<Grouping, NUM_COLUMNS> groupings;
1343
1346 CargoDataVector displayed_rows{};
1347
1349 {
1350 this->CreateNestedTree();
1351 this->vscroll = this->GetScrollbar(WID_SV_SCROLLBAR);
1352 /* Nested widget tree creation is done in two steps to ensure that this->GetWidget<NWidgetCore>(WID_SV_ACCEPTS_RATINGS) exists in UpdateWidgetSize(). */
1353 this->FinishInitNested(window_number);
1354
1355 this->groupings[0] = GR_CARGO;
1359 this->sort_orders[0] = SO_ASCENDING;
1361 this->owner = Station::Get(window_number)->owner;
1362 }
1363
1364 void Close([[maybe_unused]] int data = 0) override
1365 {
1366 CloseWindowById(WC_TRAINS_LIST, VehicleListIdentifier(VL_STATION_LIST, VEH_TRAIN, this->owner, this->window_number).ToWindowNumber(), false);
1367 CloseWindowById(WC_ROADVEH_LIST, VehicleListIdentifier(VL_STATION_LIST, VEH_ROAD, this->owner, this->window_number).ToWindowNumber(), false);
1368 CloseWindowById(WC_SHIPS_LIST, VehicleListIdentifier(VL_STATION_LIST, VEH_SHIP, this->owner, this->window_number).ToWindowNumber(), false);
1369 CloseWindowById(WC_AIRCRAFT_LIST, VehicleListIdentifier(VL_STATION_LIST, VEH_AIRCRAFT, this->owner, this->window_number).ToWindowNumber(), false);
1370
1371 SetViewportCatchmentStation(Station::Get(this->window_number), false);
1372 this->Window::Close();
1373 }
1374
1385 void ShowCargo(CargoDataEntry *data, CargoType cargo, StationID source, StationID next, StationID dest, uint count)
1386 {
1387 if (count == 0) return;
1388 bool auto_distributed = _settings_game.linkgraph.GetDistributionType(cargo) != DT_MANUAL;
1389 const CargoDataEntry *expand = &this->expanded_rows;
1390 for (int i = 0; i < NUM_COLUMNS && expand != nullptr; ++i) {
1391 switch (groupings[i]) {
1392 case GR_CARGO:
1393 assert(i == 0);
1394 data = data->InsertOrRetrieve(cargo);
1395 data->SetTransfers(source != this->window_number);
1396 expand = expand->Retrieve(cargo);
1397 break;
1398 case GR_SOURCE:
1399 if (auto_distributed || source != this->window_number) {
1400 data = data->InsertOrRetrieve(source);
1401 expand = expand->Retrieve(source);
1402 }
1403 break;
1404 case GR_NEXT:
1405 if (auto_distributed) {
1406 data = data->InsertOrRetrieve(next);
1407 expand = expand->Retrieve(next);
1408 }
1409 break;
1410 case GR_DESTINATION:
1411 if (auto_distributed) {
1412 data = data->InsertOrRetrieve(dest);
1413 expand = expand->Retrieve(dest);
1414 }
1415 break;
1416 }
1417 }
1418 data->Update(count);
1419 }
1420
1422 {
1423 switch (widget) {
1424 case WID_SV_WAITING:
1426 size.height = 4 * resize.height + padding.height;
1427 this->expand_shrink_width = std::max(GetStringBoundingBox("-").width, GetStringBoundingBox("+").width);
1428 break;
1429
1431 size.height = ((this->GetWidget<NWidgetCore>(WID_SV_ACCEPTS_RATINGS)->GetString() == STR_STATION_VIEW_RATINGS_BUTTON) ? this->accepts_lines : this->rating_lines) * GetCharacterHeight(FS_NORMAL) + padding.height;
1432 break;
1433
1435 if (!Station::Get(this->window_number)->facilities.Test(StationFacility::Airport)) {
1436 /* Hide 'Close Airport' button if no airport present. */
1437 size.width = 0;
1438 resize.width = 0;
1439 fill.width = 0;
1440 }
1441 break;
1442 }
1443 }
1444
1445 void OnPaint() override
1446 {
1447 const Station *st = Station::Get(this->window_number);
1448 CargoDataEntry cargo;
1449 BuildCargoList(&cargo, st);
1450
1451 this->vscroll->SetCount(cargo.GetNumChildren()); // update scrollbar
1452
1453 /* disable some buttons */
1459 this->SetWidgetDisabledState(WID_SV_CLOSE_AIRPORT, !st->facilities.Test(StationFacility::Airport) || st->owner != _local_company || st->owner == OWNER_NONE); // Also consider SE, where _local_company == OWNER_NONE
1461
1464 this->SetWidgetLoweredState(WID_SV_CATCHMENT, _viewport_highlight_station == st);
1465
1466 this->DrawWidgets();
1467
1468 if (!this->IsShaded()) {
1469 /* Draw 'accepted cargo' or 'cargo ratings'. */
1471 const Rect r = wid->GetCurrentRect();
1473 int lines = this->DrawAcceptedCargo(r);
1474 if (lines > this->accepts_lines) { // Resize the widget, and perform re-initialization of the window.
1475 this->accepts_lines = lines;
1476 this->ReInit();
1477 return;
1478 }
1479 } else {
1480 int lines = this->DrawCargoRatings(r);
1481 if (lines > this->rating_lines) { // Resize the widget, and perform re-initialization of the window.
1482 this->rating_lines = lines;
1483 this->ReInit();
1484 return;
1485 }
1486 }
1487
1488 /* Draw arrow pointing up/down for ascending/descending sorting */
1489 this->DrawSortButtonState(WID_SV_SORT_ORDER, sort_orders[1] == SO_ASCENDING ? SBS_UP : SBS_DOWN);
1490
1491 int pos = this->vscroll->GetPosition();
1492
1493 int maxrows = this->vscroll->GetCapacity();
1494
1495 displayed_rows.clear();
1496
1497 /* Draw waiting cargo. */
1499 Rect waiting_rect = nwi->GetCurrentRect().Shrink(WidgetDimensions::scaled.framerect);
1500 this->DrawEntries(&cargo, waiting_rect, pos, maxrows, 0);
1502 }
1503 }
1504
1505 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1506 {
1507 if (widget == WID_SV_CAPTION) {
1508 const Station *st = Station::Get(this->window_number);
1510 }
1511
1512 return this->Window::GetWidgetString(widget, stringid);
1513 }
1514
1521 {
1522 const Station *st = Station::Get(this->window_number);
1524 cargo_entry->Clear();
1525
1526 if (!st->goods[i].HasData()) return;
1527
1528 for (const auto &it : st->goods[i].GetData().flows) {
1529 StationID from = it.first;
1531 uint32_t prev_count = 0;
1532 for (const auto &flow_it : *it.second.GetShares()) {
1533 StationID via = flow_it.second;
1535 if (via == this->window_number) {
1537 } else {
1538 EstimateDestinations(i, from, via, flow_it.first - prev_count, via_entry);
1539 }
1540 prev_count = flow_it.first;
1541 }
1542 }
1543 }
1544
1554 void EstimateDestinations(CargoType cargo, StationID source, StationID next, uint count, CargoDataEntry *dest)
1555 {
1556 if (Station::IsValidID(next) && Station::IsValidID(source)) {
1557 GoodsEntry &ge = Station::Get(next)->goods[cargo];
1558 if (!ge.HasData()) return;
1559
1561 const FlowStatMap &flowmap = ge.GetData().flows;
1562 FlowStatMap::const_iterator map_it = flowmap.find(source);
1563 if (map_it != flowmap.end()) {
1564 const FlowStat::SharesMap *shares = map_it->second.GetShares();
1565 uint32_t prev_count = 0;
1566 for (FlowStat::SharesMap::const_iterator i = shares->begin(); i != shares->end(); ++i) {
1567 tmp.InsertOrRetrieve(i->second)->Update(i->first - prev_count);
1568 prev_count = i->first;
1569 }
1570 }
1571
1572 if (tmp.GetCount() == 0) {
1573 dest->InsertOrRetrieve(StationID::Invalid())->Update(count);
1574 } else {
1575 uint sum_estimated = 0;
1576 while (sum_estimated < count) {
1577 for (CargoDataSet::iterator i = tmp.Begin(); i != tmp.End() && sum_estimated < count; ++i) {
1578 CargoDataEntry *child = *i;
1579 uint estimate = DivideApprox(child->GetCount() * count, tmp.GetCount());
1580 if (estimate == 0) estimate = 1;
1581
1582 sum_estimated += estimate;
1583 if (sum_estimated > count) {
1584 estimate -= sum_estimated - count;
1585 sum_estimated = count;
1586 }
1587
1588 if (estimate > 0) {
1589 if (child->GetStation() == next) {
1590 dest->InsertOrRetrieve(next)->Update(estimate);
1591 } else {
1592 EstimateDestinations(cargo, source, child->GetStation(), estimate, dest);
1593 }
1594 }
1595 }
1596
1597 }
1598 }
1599 } else {
1600 dest->InsertOrRetrieve(StationID::Invalid())->Update(count);
1601 }
1602 }
1603
1611 {
1613 for (FlowStatMap::const_iterator it = flows.begin(); it != flows.end(); ++it) {
1614 StationID from = it->first;
1616 const FlowStat::SharesMap *shares = it->second.GetShares();
1617 for (FlowStat::SharesMap::const_iterator flow_it = shares->begin(); flow_it != shares->end(); ++flow_it) {
1619 for (CargoDataSet::iterator dest_it = via_entry->Begin(); dest_it != via_entry->End(); ++dest_it) {
1621 ShowCargo(cargo, i, from, flow_it->second, dest_entry->GetStation(), dest_entry->GetCount());
1622 }
1623 }
1624 }
1625 }
1626
1634 {
1636 for (StationCargoList::ConstIterator it = packets.Packets()->begin(); it != packets.Packets()->end(); it++) {
1637 const CargoPacket *cp = *it;
1638 StationID next = it.GetKey();
1639
1640 const CargoDataEntry *source_entry = source_dest->Retrieve(cp->GetFirstStation());
1641 if (source_entry == nullptr) {
1642 this->ShowCargo(cargo, i, cp->GetFirstStation(), next, StationID::Invalid(), cp->Count());
1643 continue;
1644 }
1645
1647 if (via_entry == nullptr) {
1648 this->ShowCargo(cargo, i, cp->GetFirstStation(), next, StationID::Invalid(), cp->Count());
1649 continue;
1650 }
1651
1652 uint remaining = cp->Count();
1653 for (CargoDataSet::iterator dest_it = via_entry->Begin(); dest_it != via_entry->End();) {
1655
1656 /* Advance iterator here instead of in the for statement to test whether this is the last entry */
1657 ++dest_it;
1658
1659 uint val;
1660 if (dest_it == via_entry->End()) {
1661 /* Allocate all remaining waiting cargo to the last destination to avoid
1662 * waiting cargo being "lost", and the displayed total waiting cargo
1663 * not matching GoodsEntry::TotalCount() */
1664 val = remaining;
1665 } else {
1666 val = std::min<uint>(remaining, DivideApprox(cp->Count() * dest_entry->GetCount(), via_entry->GetCount()));
1667 remaining -= val;
1668 }
1669 this->ShowCargo(cargo, i, cp->GetFirstStation(), next, dest_entry->GetStation(), val);
1670 }
1671 }
1672 this->ShowCargo(cargo, i, NEW_STATION, NEW_STATION, NEW_STATION, packets.ReservedCount());
1673 }
1674
1680 void BuildCargoList(CargoDataEntry *cargo, const Station *st)
1681 {
1682 for (CargoType i = 0; i < NUM_CARGO; i++) {
1683
1684 if (this->cached_destinations.Retrieve(i) == nullptr) {
1685 this->RecalcDestinations(i);
1686 }
1687
1688 const GoodsEntry &ge = st->goods[i];
1689 if (!ge.HasData()) continue;
1690
1691 if (this->current_mode == MODE_WAITING) {
1692 this->BuildCargoList(i, ge.GetData().cargo, cargo);
1693 } else {
1694 this->BuildFlowList(i, ge.GetData().flows, cargo);
1695 }
1696 }
1697 }
1698
1704 {
1705 std::list<StationID> stations;
1706 const CargoDataEntry *parent = data->GetParent();
1707 if (parent->GetParent() == nullptr) {
1708 this->displayed_rows.push_back(RowDisplay(&this->expanded_rows, data->GetCargo()));
1709 return;
1710 }
1711
1712 StationID next = data->GetStation();
1713 while (parent->GetParent()->GetParent() != nullptr) {
1714 stations.push_back(parent->GetStation());
1715 parent = parent->GetParent();
1716 }
1717
1718 CargoType cargo = parent->GetCargo();
1719 CargoDataEntry *filter = this->expanded_rows.Retrieve(cargo);
1720 while (!stations.empty()) {
1721 filter = filter->Retrieve(stations.back());
1722 stations.pop_back();
1723 }
1724
1725 this->displayed_rows.push_back(RowDisplay(filter, next));
1726 }
1727
1737 {
1738 if (station == this->window_number) {
1739 return here;
1740 } else if (station == StationID::Invalid()) {
1741 return any;
1742 } else if (station == NEW_STATION) {
1744 } else {
1745 return other_station;
1746 }
1747 }
1748
1749 StringID GetGroupingString(Grouping grouping, StationID station) const
1750 {
1751 switch (grouping) {
1755 default: NOT_REACHED();
1756 }
1757 }
1758
1767 {
1768 CargoDataEntry *parent = cd->GetParent();
1769 for (int i = column - 1; i > 0; --i) {
1770 if (this->groupings[i] == GR_DESTINATION) {
1771 if (parent->GetStation() == station) {
1773 } else {
1774 return STR_STATION_VIEW_VIA;
1775 }
1776 }
1777 parent = parent->GetParent();
1778 }
1779
1780 if (this->groupings[column + 1] == GR_DESTINATION) {
1781 CargoDataSet::iterator begin = cd->Begin();
1782 CargoDataSet::iterator end = cd->End();
1783 if (begin != end && ++(cd->Begin()) == end && (*(begin))->GetStation() == station) {
1785 } else {
1786 return STR_STATION_VIEW_VIA;
1787 }
1788 }
1789
1790 return STR_STATION_VIEW_VIA;
1791 }
1792
1803 int DrawEntries(CargoDataEntry *entry, const Rect &r, int pos, int maxrows, int column, CargoType cargo = INVALID_CARGO)
1804 {
1805 if (this->sortings[column] == CargoSortType::AsGrouping) {
1806 if (this->groupings[column] != GR_CARGO) {
1807 entry->Resort(CargoSortType::StationString, this->sort_orders[column]);
1808 }
1809 } else {
1810 entry->Resort(CargoSortType::Count, this->sort_orders[column]);
1811 }
1812 for (CargoDataSet::iterator i = entry->Begin(); i != entry->End(); ++i) {
1813 CargoDataEntry *cd = *i;
1814
1815 Grouping grouping = this->groupings[column];
1816 if (grouping == GR_CARGO) cargo = cd->GetCargo();
1817 bool auto_distributed = _settings_game.linkgraph.GetDistributionType(cargo) != DT_MANUAL;
1818
1819 if (pos > -maxrows && pos <= 0) {
1820 StringID str = STR_EMPTY;
1821 StationID station = StationID::Invalid();
1822 int y = r.top - pos * GetCharacterHeight(FS_NORMAL);
1823 if (this->groupings[column] == GR_CARGO) {
1825 DrawCargoIcons(cd->GetCargo(), cd->GetCount(), r.left + this->expand_shrink_width, r.right - this->expand_shrink_width, y);
1826 } else {
1827 if (!auto_distributed) grouping = GR_SOURCE;
1828 station = cd->GetStation();
1829 str = this->GetGroupingString(grouping, station);
1830 if (grouping == GR_NEXT && str == STR_STATION_VIEW_VIA) str = this->SearchNonStop(cd, station, column);
1831
1832 if (pos == -this->scroll_to_row && Station::IsValidID(station)) {
1834 }
1835 }
1836
1837 bool rtl = _current_text_dir == TD_RTL;
1838 Rect text = r.Indent(column * WidgetDimensions::scaled.hsep_indent, rtl).Indent(this->expand_shrink_width, !rtl);
1839 Rect shrink = r.WithWidth(this->expand_shrink_width, !rtl);
1840
1841 DrawString(text.left, text.right, y, GetString(str, cargo, cd->GetCount(), station));
1842
1843 if (column < NUM_COLUMNS - 1) {
1844 const char *sym = nullptr;
1845 if (cd->GetNumChildren() > 0) {
1846 sym = "-";
1847 } else if (auto_distributed && str != STR_STATION_VIEW_RESERVED) {
1848 sym = "+";
1849 } else {
1850 /* Only draw '+' if there is something to be shown. */
1851 const GoodsEntry &ge = Station::Get(this->window_number)->goods[cargo];
1852 if (ge.HasData()) {
1853 const StationCargoList &cargo_list = ge.GetData().cargo;
1854 if (grouping == GR_CARGO && (cargo_list.ReservedCount() > 0 || cd->HasTransfers())) {
1855 sym = "+";
1856 }
1857 }
1858 }
1859 if (sym != nullptr) DrawString(shrink.left, shrink.right, y, sym, TC_YELLOW);
1860 }
1861 this->SetDisplayedRow(cd);
1862 }
1863 --pos;
1864 if (auto_distributed || column == 0) {
1865 pos = this->DrawEntries(cd, r, pos, maxrows, column + 1, cargo);
1866 }
1867 }
1868 return pos;
1869 }
1870
1876 int DrawAcceptedCargo(const Rect &r) const
1877 {
1878 const Station *st = Station::Get(this->window_number);
1879 Rect tr = r.Shrink(WidgetDimensions::scaled.framerect);
1880
1883 }
1884
1890 int DrawCargoRatings(const Rect &r) const
1891 {
1892 const Station *st = Station::Get(this->window_number);
1893 bool rtl = _current_text_dir == TD_RTL;
1894 Rect tr = r.Shrink(WidgetDimensions::scaled.framerect);
1895
1896 if (st->town->exclusive_counter > 0) {
1899 }
1900
1903
1904 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
1905 const GoodsEntry *ge = &st->goods[cs->Index()];
1906 if (!ge->HasRating()) continue;
1907
1911 cs->name,
1912 lg != nullptr ? lg->Monthly((*lg)[ge->node].supply) : 0,
1914 ToPercent8(ge->rating)));
1916 }
1918 }
1919
1925 template <class Tid>
1927 {
1928 if (filter->Retrieve(next) != nullptr) {
1929 filter->Remove(next);
1930 } else {
1931 filter->InsertOrRetrieve(next);
1932 }
1933 }
1934
1940 {
1941 if (row < 0 || (uint)row >= this->displayed_rows.size()) return;
1942 if (_ctrl_pressed) {
1943 this->scroll_to_row = row;
1944 } else {
1945 RowDisplay &display = this->displayed_rows[row];
1946 if (display.filter == &this->expanded_rows) {
1948 } else {
1950 }
1951 }
1953 }
1954
1955 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1956 {
1957 switch (widget) {
1958 case WID_SV_WAITING:
1959 this->HandleCargoWaitingClick(this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SV_WAITING, WidgetDimensions::scaled.framerect.top) - this->vscroll->GetPosition());
1960 break;
1961
1962 case WID_SV_CATCHMENT:
1964 break;
1965
1966 case WID_SV_LOCATION:
1967 if (_ctrl_pressed) {
1968 ShowExtraViewportWindow(Station::Get(this->window_number)->xy);
1969 } else {
1970 ScrollMainWindowToTile(Station::Get(this->window_number)->xy);
1971 }
1972 break;
1973
1975 /* Swap between 'accepts' and 'ratings' view. */
1976 int height_change;
1980 height_change = this->rating_lines - this->accepts_lines;
1981 } else {
1983 height_change = this->accepts_lines - this->rating_lines;
1984 }
1985 this->ReInit(0, height_change * GetCharacterHeight(FS_NORMAL));
1986 break;
1987 }
1988
1989 case WID_SV_RENAME:
1992 break;
1993
1995 Command<CMD_OPEN_CLOSE_AIRPORT>::Post(this->window_number);
1996 break;
1997
1998 case WID_SV_TRAINS: // Show list of scheduled trains to this station
1999 case WID_SV_ROADVEHS: // Show list of scheduled road-vehicles to this station
2000 case WID_SV_SHIPS: // Show list of scheduled ships to this station
2001 case WID_SV_PLANES: { // Show list of scheduled aircraft to this station
2002 Owner owner = Station::Get(this->window_number)->owner;
2003 ShowVehicleListWindow(owner, (VehicleType)(widget - WID_SV_TRAINS), static_cast<StationID>(this->window_number));
2004 break;
2005 }
2006
2007 case WID_SV_SORT_BY: {
2008 /* The initial selection is composed of current mode and
2009 * sorting criteria for columns 1, 2, and 3. Column 0 is always
2010 * sorted by cargo type. The others can theoretically be sorted
2011 * by different things but there is no UI for that. */
2013 this->current_mode * 2 + (this->sortings[1] == CargoSortType::Count ? 1 : 0),
2014 WID_SV_SORT_BY, 0, 0);
2015 break;
2016 }
2017
2018 case WID_SV_GROUP_BY: {
2019 ShowDropDownMenu(this, StationViewWindow::group_names, this->grouping_index, WID_SV_GROUP_BY, 0, 0);
2020 break;
2021 }
2022
2023 case WID_SV_SORT_ORDER: { // flip sorting method asc/desc
2024 this->SelectSortOrder(this->sort_orders[1] == SO_ASCENDING ? SO_DESCENDING : SO_ASCENDING);
2025 this->SetTimeout();
2027 break;
2028 }
2029 }
2030 }
2031
2036 void SelectSortOrder(SortOrder order)
2037 {
2038 this->sort_orders[1] = this->sort_orders[2] = this->sort_orders[3] = order;
2040 this->SetDirty();
2041 }
2042
2047 void SelectSortBy(int index)
2048 {
2050 switch (StationViewWindow::sort_names[index]) {
2052 this->current_mode = MODE_WAITING;
2053 this->sortings[1] = this->sortings[2] = this->sortings[3] = CargoSortType::AsGrouping;
2054 break;
2056 this->current_mode = MODE_WAITING;
2057 this->sortings[1] = this->sortings[2] = this->sortings[3] = CargoSortType::Count;
2058 break;
2060 this->current_mode = MODE_PLANNED;
2061 this->sortings[1] = this->sortings[2] = this->sortings[3] = CargoSortType::AsGrouping;
2062 break;
2064 this->current_mode = MODE_PLANNED;
2065 this->sortings[1] = this->sortings[2] = this->sortings[3] = CargoSortType::Count;
2066 break;
2067 default:
2068 NOT_REACHED();
2069 }
2070 /* Display the current sort variant */
2072 this->SetDirty();
2073 }
2074
2079 void SelectGroupBy(int index)
2080 {
2081 this->grouping_index = index;
2084 switch (StationViewWindow::group_names[index]) {
2086 this->groupings[1] = GR_SOURCE;
2087 this->groupings[2] = GR_NEXT;
2088 this->groupings[3] = GR_DESTINATION;
2089 break;
2091 this->groupings[1] = GR_SOURCE;
2092 this->groupings[2] = GR_DESTINATION;
2093 this->groupings[3] = GR_NEXT;
2094 break;
2096 this->groupings[1] = GR_NEXT;
2097 this->groupings[2] = GR_SOURCE;
2098 this->groupings[3] = GR_DESTINATION;
2099 break;
2101 this->groupings[1] = GR_NEXT;
2102 this->groupings[2] = GR_DESTINATION;
2103 this->groupings[3] = GR_SOURCE;
2104 break;
2106 this->groupings[1] = GR_DESTINATION;
2107 this->groupings[2] = GR_SOURCE;
2108 this->groupings[3] = GR_NEXT;
2109 break;
2111 this->groupings[1] = GR_DESTINATION;
2112 this->groupings[2] = GR_NEXT;
2113 this->groupings[3] = GR_SOURCE;
2114 break;
2115 }
2116 this->SetDirty();
2117 }
2118
2119 void OnDropdownSelect(WidgetID widget, int index) override
2120 {
2121 if (widget == WID_SV_SORT_BY) {
2122 this->SelectSortBy(index);
2123 } else {
2124 this->SelectGroupBy(index);
2125 }
2126 }
2127
2128 void OnQueryTextFinished(std::optional<std::string> str) override
2129 {
2130 if (!str.has_value()) return;
2131
2133 }
2134
2135 void OnResize() override
2136 {
2137 this->vscroll->SetCapacityFromWidget(this, WID_SV_WAITING, WidgetDimensions::scaled.framerect.Vertical());
2138 }
2139
2145 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
2146 {
2147 if (gui_scope) {
2148 if (data >= 0 && data < NUM_CARGO) {
2150 } else {
2151 this->ReInit();
2152 }
2153 }
2154 }
2155};
2156
2157static WindowDesc _station_view_desc(
2158 WDP_AUTO, "view_station", 249, 117,
2160 {},
2161 _nested_station_view_widgets
2162);
2163
2170{
2171 AllocateWindowDescFront<StationViewWindow>(_station_view_desc, station);
2172}
2173
2179
2180static std::vector<TileAndStation> _deleted_stations_nearby;
2181static std::vector<StationID> _stations_nearby_list;
2182
2190template <class T>
2191static bool AddNearbyStation(TileIndex tile, void *user_data)
2192{
2193 TileArea *ctx = (TileArea *)user_data;
2194
2195 /* First check if there were deleted stations here */
2196 for (auto it = _deleted_stations_nearby.begin(); it != _deleted_stations_nearby.end(); /* nothing */) {
2197 if (it->tile == tile) {
2198 _stations_nearby_list.push_back(it->station);
2199 it = _deleted_stations_nearby.erase(it);
2200 } else {
2201 ++it;
2202 }
2203 }
2204
2205 /* Check if own station and if we stay within station spread */
2206 if (!IsTileType(tile, MP_STATION)) return false;
2207
2208 StationID sid = GetStationIndex(tile);
2209
2210 /* This station is (likely) a waypoint */
2211 if (!T::IsValidID(sid)) return false;
2212
2213 BaseStation *st = BaseStation::Get(sid);
2214 if (st->owner != _local_company || std::ranges::find(_stations_nearby_list, sid) != _stations_nearby_list.end()) return false;
2215
2216 if (st->rect.BeforeAddRect(ctx->tile, ctx->w, ctx->h, StationRect::ADD_TEST).Succeeded()) {
2217 _stations_nearby_list.push_back(sid);
2218 }
2219
2220 return false; // We want to include *all* nearby stations
2221}
2222
2232template <class T>
2233static const BaseStation *FindStationsNearby(TileArea ta, bool distant_join)
2234{
2235 TileArea ctx = ta;
2236
2237 _stations_nearby_list.clear();
2238 _stations_nearby_list.push_back(NEW_STATION);
2239 _deleted_stations_nearby.clear();
2240
2241 /* Check the inside, to return, if we sit on another station */
2242 for (TileIndex t : ta) {
2243 if (t < Map::Size() && IsTileType(t, MP_STATION) && T::IsValidID(GetStationIndex(t))) return BaseStation::GetByTile(t);
2244 }
2245
2246 /* Look for deleted stations */
2247 for (const BaseStation *st : BaseStation::Iterate()) {
2248 if (T::IsValidBaseStation(st) && !st->IsInUse() && st->owner == _local_company) {
2249 /* Include only within station spread (yes, it is strictly less than) */
2250 if (std::max(DistanceMax(ta.tile, st->xy), DistanceMax(TileAddXY(ta.tile, ta.w - 1, ta.h - 1), st->xy)) < _settings_game.station.station_spread) {
2251 _deleted_stations_nearby.push_back({st->xy, st->index});
2252
2253 /* Add the station when it's within where we're going to build */
2254 if (IsInsideBS(TileX(st->xy), TileX(ctx.tile), ctx.w) &&
2255 IsInsideBS(TileY(st->xy), TileY(ctx.tile), ctx.h)) {
2256 AddNearbyStation<T>(st->xy, &ctx);
2257 }
2258 }
2259 }
2260 }
2261
2262 /* Only search tiles where we have a chance to stay within the station spread.
2263 * The complete check needs to be done in the callback as we don't know the
2264 * extent of the found station, yet. */
2265 if (distant_join && std::min(ta.w, ta.h) >= _settings_game.station.station_spread) return nullptr;
2266 uint max_dist = distant_join ? _settings_game.station.station_spread - std::min(ta.w, ta.h) : 1;
2267
2268 TileIndex tile = TileAddByDir(ctx.tile, DIR_N);
2269 CircularTileSearch(&tile, max_dist, ta.w, ta.h, AddNearbyStation<T>, &ctx);
2270
2271 return nullptr;
2272}
2273
2274static constexpr NWidgetPart _nested_select_station_widgets[] = {
2276 NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
2277 NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_JS_CAPTION), SetStringTip(STR_JOIN_STATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2278 NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
2279 EndContainer(),
2281 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_JS_PANEL), SetResize(1, 0), SetScrollbar(WID_JS_SCROLLBAR), EndContainer(),
2283 NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_JS_SCROLLBAR),
2284 NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
2285 EndContainer(),
2286 EndContainer(),
2287};
2288
2293template <class T>
2295 StationPickerCmdProc select_station_proc{};
2297 Scrollbar *vscroll = nullptr;
2298
2299 SelectStationWindow(WindowDesc &desc, TileArea ta, StationPickerCmdProc&& proc) :
2300 Window(desc),
2301 select_station_proc(std::move(proc)),
2302 area(ta)
2303 {
2304 this->CreateNestedTree();
2305 this->vscroll = this->GetScrollbar(WID_JS_SCROLLBAR);
2306 this->GetWidget<NWidgetCore>(WID_JS_CAPTION)->SetString(T::IsWaypoint() ? STR_JOIN_WAYPOINT_CAPTION : STR_JOIN_STATION_CAPTION);
2307 this->FinishInitNested(0);
2308 this->OnInvalidateData(0);
2309
2310 _thd.freeze = true;
2311 }
2312
2313 void Close([[maybe_unused]] int data = 0) override
2314 {
2316
2317 _thd.freeze = false;
2318 this->Window::Close();
2319 }
2320
2322 {
2323 if (widget != WID_JS_PANEL) return;
2324
2325 /* Determine the widest string */
2327 for (const auto &station : _stations_nearby_list) {
2328 if (station == NEW_STATION) continue;
2329 const BaseStation *st = BaseStation::Get(station);
2330 d = maxdim(d, GetStringBoundingBox(T::IsWaypoint()
2333 }
2334
2335 resize.height = d.height;
2336 d.height *= 5;
2337 d.width += padding.width;
2338 d.height += padding.height;
2339 size = d;
2340 }
2341
2342 void DrawWidget(const Rect &r, WidgetID widget) const override
2343 {
2344 if (widget != WID_JS_PANEL) return;
2345
2346 Rect tr = r.Shrink(WidgetDimensions::scaled.framerect);
2347 auto [first, last] = this->vscroll->GetVisibleRangeIterators(_stations_nearby_list);
2348 for (auto it = first; it != last; ++it, tr.top += this->resize.step_height) {
2349 if (*it == NEW_STATION) {
2351 } else {
2352 const BaseStation *st = BaseStation::Get(*it);
2353 DrawString(tr, T::IsWaypoint()
2356 }
2357 }
2358
2359 }
2360
2361 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
2362 {
2363 if (widget != WID_JS_PANEL) return;
2364
2365 auto it = this->vscroll->GetScrolledItemFromWidget(_stations_nearby_list, pt.y, this, WID_JS_PANEL, WidgetDimensions::scaled.framerect.top);
2366 if (it == _stations_nearby_list.end()) return;
2367
2368 /* Execute stored Command */
2369 this->select_station_proc(false, *it);
2370
2371 /* Close Window; this might cause double frees! */
2373 }
2374
2375 void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
2376 {
2377 if (_thd.dirty & 2) {
2378 _thd.dirty &= ~2;
2379 this->SetDirty();
2380 }
2381 }
2382
2383 void OnResize() override
2384 {
2385 this->vscroll->SetCapacityFromWidget(this, WID_JS_PANEL, WidgetDimensions::scaled.framerect.Vertical());
2386 }
2387
2393 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
2394 {
2395 if (!gui_scope) return;
2396 FindStationsNearby<T>(this->area, true);
2397 this->vscroll->SetCount(_stations_nearby_list.size());
2398 this->SetDirty();
2399 }
2400
2401 void OnMouseOver([[maybe_unused]] Point pt, WidgetID widget) override
2402 {
2403 if (widget != WID_JS_PANEL) {
2405 return;
2406 }
2407
2408 /* Show coverage area of station under cursor */
2409 auto it = this->vscroll->GetScrolledItemFromWidget(_stations_nearby_list, pt.y, this, WID_JS_PANEL, WidgetDimensions::scaled.framerect.top);
2410 const typename T::StationType *st = it == _stations_nearby_list.end() || *it == NEW_STATION ? nullptr : T::StationType::Get(*it);
2412 }
2413};
2414
2415static WindowDesc _select_station_desc(
2416 WDP_AUTO, "build_station_join", 200, 180,
2419 _nested_select_station_widgets
2420);
2421
2422
2430template <class T>
2431static bool StationJoinerNeeded(TileArea ta, const StationPickerCmdProc &proc)
2432{
2433 /* Only show selection if distant join is enabled in the settings */
2434 if (!_settings_game.station.distant_join_stations) return false;
2435
2436 /* If a window is already opened and we didn't ctrl-click,
2437 * return true (i.e. just flash the old window) */
2438 Window *selection_window = FindWindowById(WC_SELECT_STATION, 0);
2439 if (selection_window != nullptr) {
2440 /* Abort current distant-join and start new one */
2441 selection_window->Close();
2443 }
2444
2445 /* only show the popup, if we press ctrl */
2446 if (!_ctrl_pressed) return false;
2447
2448 /* Now check if we could build there */
2449 if (!proc(true, StationID::Invalid())) return false;
2450
2451 return FindStationsNearby<T>(ta, false) == nullptr;
2452}
2453
2460template <class T>
2461void ShowSelectBaseStationIfNeeded(TileArea ta, StationPickerCmdProc&& proc)
2462{
2463 if (StationJoinerNeeded<T>(ta, proc)) {
2465 new SelectStationWindow<T>(_select_station_desc, ta, std::move(proc));
2466 } else {
2467 proc(false, StationID::Invalid());
2468 }
2469}
2470
2476void ShowSelectStationIfNeeded(TileArea ta, StationPickerCmdProc proc)
2477{
2478 ShowSelectBaseStationIfNeeded<StationTypeFilter>(ta, std::move(proc));
2479}
2480
2486void ShowSelectRailWaypointIfNeeded(TileArea ta, StationPickerCmdProc proc)
2487{
2488 ShowSelectBaseStationIfNeeded<RailWaypointTypeFilter>(ta, std::move(proc));
2489}
2490
2496void ShowSelectRoadWaypointIfNeeded(TileArea ta, StationPickerCmdProc proc)
2497{
2498 ShowSelectBaseStationIfNeeded<RoadWaypointTypeFilter>(ta, std::move(proc));
2499}
@ AirportClosed
Dummy block for indicating a closed airport.
debug_inline constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
constexpr T SetBit(T &x, const uint8_t y)
Set a bit in a variable.
constexpr uint8_t FindFirstBit(T x)
Search the first set bit in a value.
constexpr uint CountBits(T value)
Counts the number of set bits in a variable.
constexpr T ToggleBit(T &x, const uint8_t y)
Toggles a bit in a variable.
uint8_t CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:23
static const CargoType NUM_CARGO
Maximum number of cargo types in a game.
Definition cargo_type.h:75
Dimension GetLargestCargoIconSize()
Get dimensions of largest cargo icon.
std::span< const CargoSpec * > _sorted_standard_cargo_specs
Standard cargo specifications sorted alphabetically by name.
std::vector< const CargoSpec * > _sorted_cargo_specs
Cargo specifications sorted alphabetically by name.
CargoTypes _cargo_mask
Bitmask of cargo types available.
Definition cargotype.cpp:33
Types/functions related to cargoes.
bool IsCargoInClass(CargoType c, CargoClasses cc)
Does cargo c have cargo class cc?
Definition cargotype.h:236
@ Passengers
Passengers.
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr Tstorage base() const noexcept
Retrieve the raw value behind this bit set.
constexpr Timpl & Flip(Tvalue_type value)
Flip the value-th bit.
constexpr Timpl & Set()
Set all bits.
constexpr bool Any(const Timpl &other) const
Test if any of the given values are set.
A cargo data entry representing one possible row in the station view window's top part.
uint count
sum of counts of all children or amount of cargo for this entry.
void Clear()
Delete all subentries, reset count and num_children and adapt parent's count.
StationID GetStation() const
Get the station ID for this entry.
void IncrementSize()
Increment.
void SetTransfers(bool value)
Set the transfers state.
CargoType GetCargo() const
Get the cargo type for this entry.
uint num_children
the number of subentries belonging to this entry.
void Remove(CargoType cargo)
Remove a child associated with the given cargo.
CargoDataEntry * Retrieve(StationID station) const
Retrieve a child for the given station.
uint GetCount() const
Get the cargo count for this entry.
CargoDataEntry * GetParent() const
Get the parent entry for this entry.
StationID station
ID of the station this entry is associated with.
CargoType cargo
ID of the cargo this entry is associated with.
CargoDataSet * children
the children of this entry.
CargoDataEntry * InsertOrRetrieve(CargoType cargo)
Insert a new child or retrieve an existing child using a cargo type as ID.
CargoDataSet::iterator End() const
Get an iterator pointing to the end of the set of children.
void Update(uint count)
Update the count for this entry and propagate the change to the parent entry if there is one.
bool transfers
If there are transfers for this cargo.
CargoDataSet::iterator Begin() const
Get an iterator pointing to the begin of the set of children.
bool HasTransfers() const
Has this entry transfers.
CargoDataEntry * Retrieve(CargoType cargo) const
Retrieve a child for the given cargo.
CargoDataEntry * parent
the parent of this entry.
void Remove(StationID station)
Remove a child associated with the given station.
uint GetNumChildren() const
Get the number of children for this entry.
CargoDataEntry * InsertOrRetrieve(StationID station)
Insert a new child or retrieve an existing child using a station ID as ID.
const Tcont * Packets() const
Returns a pointer to the cargo packet list (so you can iterate over it etc).
Tcont::const_iterator ConstIterator
The const iterator for our container.
bool Succeeded() const
Did this command succeed?
The list of stations per company.
static bool StationRatingMaxSorter(const Station *const &a, const Station *const &b, const CargoTypes &cargo_filter)
Sort stations by their rating.
std::array< uint16_t, NUM_CARGO > stations_per_cargo_type
Number of stations with a rating for each cargo type.
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
void OnResize() override
Called after the window got resized.
void SortStationsList()
Sort the stations list.
static bool StationRatingMinSorter(const Station *const &a, const Station *const &b, const CargoTypes &cargo_filter)
Sort stations by their rating.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
void OnDropdownSelect(int widget, int index) override
A dropdown option associated to this window has been selected.
static bool StationWaitingAvailableSorter(const Station *const &a, const Station *const &b, const CargoTypes &cargo_filter)
Sort stations by their available waiting cargo.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
Get the raw string for a widget.
void OnPaint() override
The window must be repainted.
static bool StationNameSorter(const Station *const &a, const Station *const &b, const CargoTypes &)
Sort stations by their name.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
static bool StationTypeSorter(const Station *const &a, const Station *const &b, const CargoTypes &)
Sort stations by their type.
void OnGameTick() override
Called once per (game) tick.
static bool StationWaitingTotalSorter(const Station *const &a, const Station *const &b, const CargoTypes &cargo_filter)
Sort stations by their waiting cargo.
void BuildStationsList(const Owner owner)
(Re)Build station list
uint16_t stations_per_cargo_type_no_rating
Number of stations without a rating.
Drop down checkmark component.
Drop down string component.
Flow descriptions by origin stations.
List template of 'things' T to sort in a GUI.
void RebuildDone()
Notify the sortlist that the rebuild is done.
void SetListing(Listing l)
Import sort conditions.
bool IsDescSortOrder() const
Check if the sort order is descending.
void ToggleSortOrder()
Toggle the sort order Since that is the worst condition for the sort function reverse the list here.
bool NeedRebuild() const
Check if a rebuild is needed.
void ForceRebuild()
Force that a rebuild is needed.
bool Sort(Comp compare)
Sort the list.
void ForceResort()
Force a resort next Sort call Reset the resort timer if used too.
uint8_t SortType() const
Get the sorttype of the list.
Listing GetListing() const
Export current sort conditions.
void SetSortFuncs(std::span< SortFunction *const > n_funcs)
Hand the sort function pointers to the GUIList.
bool NeedResort()
Check if a resort is needed next loop If used the resort timer will decrease every call till 0.
void SetSortType(uint8_t n_type)
Set the sorttype of the list.
A connected component of a link graph.
Definition linkgraph.h:37
Baseclass for nested widgets.
Base class for a 'real' widget.
void SetStringTip(StringID string, StringID tool_tip)
Set string and tool tip of the nested widget.
Definition widget.cpp:1138
Scrollbar data structure.
size_type GetCapacity() const
Gets the number of visible elements of the scrollbar.
void SetCount(size_t num)
Sets the number of elements in the list.
auto GetScrolledItemFromWidget(Tcontainer &container, int clickpos, const Window *const w, WidgetID widget, int padding=0, int line_height=-1) const
Return an iterator pointing to the element of a scrolled widget that a user clicked in.
size_type GetScrolledRowFromWidget(int clickpos, const Window *const w, WidgetID widget, int padding=0, int line_height=-1) const
Compute the row of a scrolled widget that a user clicked in.
Definition widget.cpp:2447
void SetCapacityFromWidget(Window *w, WidgetID widget, int padding=0)
Set capacity of visible elements from the size and resize properties of a widget.
Definition widget.cpp:2521
size_type GetCount() const
Gets the number of elements in the list.
auto GetVisibleRangeIterators(Tcontainer &container) const
Get a pair of iterators for the range of visible elements in a container.
size_type GetPosition() const
Gets the position of the first visible element in the list.
CargoList that is used for stations.
uint ReservedCount() const
Returns sum of cargo reserved for loading onto vehicles.
static bool UsingWallclockUnits(bool newgame=false)
Check if we are using wallclock units.
RectPadding framerect
Standard padding inside many panels.
Definition window_gui.h:40
static WidgetDimensions scaled
Widget dimensions scaled for current zoom level.
Definition window_gui.h:29
int vsep_wide
Wide vertical spacing.
Definition window_gui.h:60
int hsep_wide
Wide horizontal spacing.
Definition window_gui.h:62
int hsep_normal
Normal horizontal spacing.
Definition window_gui.h:61
int hsep_indent
Width of indentation for tree layouts.
Definition window_gui.h:63
Functions related to commands.
Definition of stuff that is very close to a company, like the company struct itself.
CompanyID _local_company
Company controlled by the human player at this client. Can also be COMPANY_SPECTATOR.
Functions related to companies.
static constexpr Owner OWNER_NONE
The tile has no ownership.
int DivideApprox(int a, int b)
Deterministic approximate division.
Definition math_func.cpp:22
Functions related to debugging.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
@ DIR_N
North.
void ShowDropDownMenu(Window *w, std::span< const StringID > strings, int selected, WidgetID button, uint32_t disabled_mask, uint32_t hidden_mask, uint width)
Show a dropdown menu window near a widget of the parent window.
Definition dropdown.cpp:441
void ShowDropDownList(Window *w, DropDownList &&list, int selected, WidgetID button, uint width, bool instant_close, bool persist)
Show a drop down list.
Definition dropdown.cpp:404
Common drop down list components.
Functions related to the drop down widget.
Types related to the drop down widget.
std::vector< std::unique_ptr< const DropDownListItem > > DropDownList
A drop down list is a collection of drop down list items.
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Definition fontcache.cpp:77
Dimension maxdim(const Dimension &d1, const Dimension &d2)
Compute bounding box of both dimensions.
Geometry functions.
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:852
Dimension GetStringListBoundingBox(std::span< const StringID > list, FontSize fontsize)
Get maximum dimension of a list of strings.
Definition gfx.cpp:890
int DrawString(int left, int right, int top, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
Definition gfx.cpp:658
bool _ctrl_pressed
Is Ctrl pressed?
Definition gfx.cpp:38
void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
Definition gfx.cpp:115
void DrawSprite(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub, ZoomLevel zoom)
Draw a sprite, not in a viewport.
Definition gfx.cpp:989
int DrawStringMultiLine(int left, int right, int top, int bottom, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly over multiple lines.
Definition gfx.cpp:775
uint32_t SpriteID
The number of a sprite, without mapping bits and colourtables.
Definition gfx_type.h:17
@ FS_SMALL
Index of the small font in the font tables.
Definition gfx_type.h:244
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:243
@ SA_CENTER
Center both horizontally and vertically.
Definition gfx_type.h:385
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:294
constexpr NWidgetPart SetFill(uint16_t fill_x, uint16_t fill_y)
Widget part function for setting filling.
constexpr NWidgetPart SetSpriteTip(SpriteID sprite, StringID tip={})
Widget part function for setting the sprite and tooltip.
constexpr NWidgetPart SetScrollbar(WidgetID index)
Attach a scrollbar to a widget.
constexpr NWidgetPart SetStringTip(StringID string, StringID tip={})
Widget part function for setting the string and tooltip.
constexpr NWidgetPart SetAspect(float ratio, AspectFlags flags=AspectFlag::ResizeX)
Widget part function for setting the aspect ratio.
constexpr NWidgetPart SetTextStyle(TextColour colour, FontSize size=FS_NORMAL)
Widget part function for setting the text style.
constexpr NWidgetPart SetMinimalSize(int16_t x, int16_t y)
Widget part function for setting the minimal size.
constexpr NWidgetPart NWidget(WidgetType tp, Colours col, WidgetID idx=-1)
Widget part function for starting a new 'real' widget.
constexpr NWidgetPart SetToolTip(StringID tip)
Widget part function for setting tooltip and clearing the widget data.
constexpr NWidgetPart EndContainer()
Widget part function for denoting the end of a container (horizontal, vertical, WWT_FRAME,...
constexpr NWidgetPart SetResize(int16_t dx, int16_t dy)
Widget part function for setting the resize step.
void SetDirty() const
Mark entire window as dirty (in need of re-paint)
Definition window.cpp:943
GUI functions that shouldn't be here.
void ShowExtraViewportWindow(TileIndex tile=INVALID_TILE)
Show a new Extra Viewport window.
Declaration of link graph classes used for cargo distribution.
@ DT_MANUAL
Manual distribution. No link graph calculations are run.
bool CircularTileSearch(TileIndex *tile, uint size, TestTileOnSearchProc proc, void *user_data)
Function performing a search around a center tile and going outward, thus in circle.
Definition map.cpp:243
uint DistanceMax(TileIndex t0, TileIndex t1)
Gets the biggest distance component (x or y) between the two given tiles.
Definition map.cpp:174
TileIndex TileAddXY(TileIndex tile, int x, int y)
Adds a given offset to a tile.
Definition map_func.h:469
TileIndex TileAddByDir(TileIndex tile, Direction dir)
Adds a Direction to a tile.
Definition map_func.h:598
static debug_inline TileIndex TileXY(uint x, uint y)
Returns the TileIndex of a coordinate.
Definition map_func.h:372
static debug_inline uint TileY(TileIndex tile)
Get the Y component of a tile.
Definition map_func.h:424
static debug_inline uint TileX(TileIndex tile)
Get the X component of a tile.
Definition map_func.h:414
static debug_inline TileIndex TileVirtXY(uint x, uint y)
Get a tile from the virtual XY-coordinate.
Definition map_func.h:403
constexpr bool IsInsideBS(const T x, const size_t base, const size_t size)
Checks if a value is between a window started at some base point.
constexpr uint CeilDiv(uint a, uint b)
Computes ceil(a / b) for non-negative a and b.
constexpr uint ToPercent8(uint i)
Converts a "fract" value 0..255 to "percent" value 0..100.
void ShowQueryString(std::string_view str, StringID caption, uint maxsize, Window *parent, CharSetFilter afilter, QueryStringFlags flags)
Show a query popup window with a textbox in it.
static constexpr CargoType CF_EXPAND_LIST
Expand list to show all items (station list)
Definition cargo_type.h:102
static constexpr CargoType CF_NO_RATING
Show items with no rating (station list)
Definition cargo_type.h:100
static constexpr CargoType CF_SELECT_ALL
Select all items (station list)
Definition cargo_type.h:101
@ Invalid
GRF is unusable with this version of OpenTTD.
TextColour GetContrastColour(uint8_t background, uint8_t threshold)
Determine a contrasty text colour for a coloured background.
Definition palette.cpp:360
static const uint8_t PC_GREEN
Green palette colour.
static const uint8_t PC_RED
Red palette colour.
A number of safeguards to prevent using unsafe methods.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:58
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:57
Base types for having sorted lists in GUIs.
@ Resort
instruct the code to resort the list in the next loop
Base classes/functions for stations.
CargoArray GetAcceptanceAroundTiles(TileIndex center_tile, int w, int h, int rad, CargoTypes *always_accepted)
Get the acceptance of cargoes around the tile in 1/8.
bool HasStationInUse(StationID station, bool include_company, CompanyID company)
Tests whether the company's vehicles have this station in orders.
CargoTypes GetAcceptanceMask(const Station *st)
Get a mask of the cargo types that the station accepts.
CargoArray GetProductionAroundTiles(TileIndex north_tile, int w, int h, int rad)
Get the cargo types being produced around the tile (in a rectangle).
Command definitions related to stations.
void ShowSelectStationIfNeeded(TileArea ta, StationPickerCmdProc proc)
Show the station selection window when needed.
static bool AddNearbyStation(TileIndex tile, void *user_data)
Add station on this tile to _stations_nearby_list if it's fully within the station spread.
CargoSortType
@ StationID
by station id
@ CargoType
by cargo type
@ AsGrouping
by the same principle the entries are being grouped
@ StationString
by station name
@ Count
by amount of cargo
void ShowSelectRailWaypointIfNeeded(TileArea ta, StationPickerCmdProc proc)
Show the rail waypoint selection window when needed.
static void DrawCargoIcons(CargoType i, uint waiting, int left, int right, int y)
Draws icons of waiting cargo in the StationView window.
static void StationsWndShowStationRating(int left, int right, int y, CargoType type, uint amount, uint8_t rating)
Draw small boxes of cargo amount and ratings data at the given coordinates.
void FindStationsAroundSelection()
Find stations adjacent to the current tile highlight area, so that existing coverage area can be draw...
int DrawStationCoverageAreaText(const Rect &r, StationCoverageType sct, int rad, bool supplies)
Calculates and draws the accepted or supplied cargo around the selected tile(s)
void ShowStationViewWindow(StationID station)
Opens StationViewWindow for given station.
static const BaseStation * FindStationsNearby(TileArea ta, bool distant_join)
Circulate around the to-be-built station to find stations we could join.
void CheckRedrawStationCoverage(const Window *w)
Check whether we need to redraw the station coverage text.
void ShowCompanyStations(CompanyID company)
Opens window with list of company's stations.
void ShowSelectBaseStationIfNeeded(TileArea ta, StationPickerCmdProc &&proc)
Show the station selection window when needed.
static bool StationJoinerNeeded(TileArea ta, const StationPickerCmdProc &proc)
Check whether we need to show the station selection window.
void ShowSelectRoadWaypointIfNeeded(TileArea ta, StationPickerCmdProc proc)
Show the road waypoint selection window when needed.
Contains enums and function declarations connected with stations GUI.
StationCoverageType
Types of cargo to display for station coverage.
Definition station_gui.h:21
@ SCT_NON_PASSENGERS_ONLY
Draw all non-passenger class cargoes.
Definition station_gui.h:23
@ SCT_PASSENGERS_ONLY
Draw only passenger class cargoes.
Definition station_gui.h:22
@ SCT_ALL
Draw all cargoes.
Definition station_gui.h:24
StationID GetStationIndex(Tile t)
Get StationID from a tile.
Definition station_map.h:28
StationFacility
The facilities a station might be having.
@ Dock
Station with a dock.
@ TruckStop
Station with truck stops.
@ Train
Station with train station.
@ Airport
Station with an airport.
@ BusStop
Station with bus stops.
static const uint MAX_LENGTH_STATION_NAME_CHARS
The maximum length of a station name in characters including '\0'.
Types related to the station widgets.
@ WID_SV_CLOSE_AIRPORT
'Close airport' button.
@ WID_SV_SORT_ORDER
'Sort order' button
@ WID_SV_CATCHMENT
Toggle catchment area highlight.
@ WID_SV_ROADVEHS
List of scheduled road vehs button.
@ WID_SV_SCROLLBAR
Scrollbar.
@ WID_SV_CAPTION
Caption of the window.
@ WID_SV_GROUP
label for "group by"
@ WID_SV_RENAME
'Rename' button.
@ WID_SV_SORT_BY
'Sort by' button
@ WID_SV_GROUP_BY
'Group by' button
@ WID_SV_PLANES
List of scheduled planes button.
@ WID_SV_ACCEPT_RATING_LIST
List of accepted cargoes / rating of cargoes.
@ WID_SV_WAITING
List of waiting cargo.
@ WID_SV_SHIPS
List of scheduled ships button.
@ WID_SV_LOCATION
'Location' button.
@ WID_SV_TRAINS
List of scheduled trains button.
@ WID_SV_ACCEPTS_RATINGS
'Accepts' / 'Ratings' button.
@ WID_STL_CAPTION
Caption of the window.
@ WID_STL_TRUCK
'TRUCK' button - list only facilities where is a truck stop.
@ WID_STL_SCROLLBAR
Scrollbar next to the main panel.
@ WID_STL_SORTDROPBTN
Dropdown button.
@ WID_STL_SORTBY
'Sort by' button - reverse sort direction.
@ WID_STL_TRAIN
'TRAIN' button - list only facilities where is a railroad station.
@ WID_STL_BUS
'BUS' button - list only facilities where is a bus stop.
@ WID_STL_FACILALL
'ALL' button - list all facilities.
@ WID_STL_LIST
The main panel, list of stations.
@ WID_STL_SHIP
'SHIP' button - list only facilities where is a dock.
@ WID_STL_CARGODROPDOWN
Cargo type dropdown list.
@ WID_STL_AIRPLANE
'AIRPLANE' button - list only facilities where is an airport.
Definition of base types and functions in a cross-platform compatible way.
int StrNaturalCompare(std::string_view s1, std::string_view s2, bool ignore_garbage_at_front)
Compares two strings using case insensitive natural sort.
Definition string.cpp:607
Functions related to low-level strings.
@ CS_ALPHANUMERAL
Both numeric and alphabetic and spaces and stuff.
Definition string_type.h:25
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:426
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:56
Functions related to OTTD's strings.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
@ TD_RTL
Text is written right-to-left by default.
AirportBlocks blocks
stores which blocks on the airport are taken. was 16 bit earlier on, then 32
Base class for all station-ish types.
TileIndex xy
Base tile of the station.
StationFacilities facilities
The facilities that this station has.
Owner owner
The owner of this station.
StationRect rect
NOSAVE: Station spread out rectangle maintained by StationRect::xxx() functions.
Town * town
The town this station is associated with.
static BaseStation * GetByTile(TileIndex tile)
Get the base station belonging to a specific tile.
Class for storing amounts of cargo.
Definition cargo_type.h:113
Container for cargo from the same location and time.
Definition cargopacket.h:41
Specification of a cargo type.
Definition cargotype.h:74
static CargoSpec * Get(size_t index)
Retrieve cargo details for the given cargo type.
Definition cargotype.h:137
StringID abbrev
Two letter abbreviation for this cargo type.
Definition cargotype.h:95
SpriteID GetCargoIcon() const
Get sprite for showing cargo of this type.
StringID name
Name of this type of cargo.
Definition cargotype.h:91
bool IsValid() const
Tests for validity of this cargospec.
Definition cargotype.h:118
GUISettings gui
settings related to the GUI
CargoTypes cargoes
bitmap of cargo types to include
StationFacilities facilities
types of stations of interest
bool include_no_rating
Whether we should include stations with no cargo rating.
Dimensions (a width and height) of a rectangle in 2D.
bool persistent_buildingtools
keep the building tools active after usage
uint8_t station_gui_group_order
the order of grouping cargo entries in the station gui
uint8_t station_gui_sort_order
the sort order of entries in the station gui - ascending or descending
bool station_show_coverage
whether to highlight coverage area
uint8_t station_gui_sort_by
sort cargo entries in the station gui by station name or amount
StationSettings station
settings related to station management
LinkGraphSettings linkgraph
settings for link graph calculations
FlowStatMap flows
Planned flows through this station.
StationCargoList cargo
The cargo packets of cargo waiting in this station.
Stores station stats for a single cargo.
bool HasRating() const
Does this cargo have a rating at this station?
debug_inline const GoodsEntryData & GetData() const
Get optional cargo packet/flow data.
NodeID node
ID of node in link graph referring to this goods entry.
LinkGraphID link_graph
Link graph this station belongs to.
uint8_t rating
Station rating for this cargo.
debug_inline bool HasData() const
Test if this goods entry has optional cargo packet/flow data.
Data structure describing how to show the list (what sort direction and criteria).
static uint MaxY()
Gets the maximum Y coordinate within the map, including MP_VOID.
Definition map_func.h:305
static debug_inline uint Size()
Get the size of the map.
Definition map_func.h:287
static debug_inline uint MaxX()
Gets the maximum X coordinate within the map, including MP_VOID.
Definition map_func.h:296
Partial widget specification to allow NWidgets to be written nested.
Represents the covered area of e.g.
uint16_t w
The width of the area.
TileIndex tile
The base tile of the area.
uint16_t h
The height of the area.
Coordinates of a point in 2D.
static Pool::IterateWrapper< Titem > Iterate(size_t from=0)
Returns an iterable ensemble of all valid Titem.
static Titem * Get(auto index)
Returns Titem with given index.
Tindex index
Index of this pool item.
static bool IsValidID(auto index)
Tests whether given index can be used to get valid (non-nullptr) Titem.
static Titem * GetIfValid(auto index)
Returns Titem with given index.
Specification of a rectangle with absolute coordinates of all edges.
Rect WithWidth(int width, bool end) const
Copy Rect and set its width.
Rect Shrink(int s) const
Copy and shrink Rect by s pixels.
Rect Indent(int indent, bool end) const
Copy Rect and indent it from its position.
uint step_height
Step-size of height resize changes.
Definition window_gui.h:213
Window for selecting stations/waypoints to (distant) join to.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
void OnRealtimeTick(uint delta_ms) override
Called periodically.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
void OnResize() override
Called after the window got resized.
void Close(int data=0) override
Hide the window and all its child windows, and mark them for a later deletion.
void OnMouseOver(Point pt, WidgetID widget) override
The mouse is currently moving over the window or has just moved outside of the window.
TileArea area
Location of new station.
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
Iterable ensemble of each set bit in a value.
static bool IsValidID(auto index)
Tests whether given index is a valid index for station of this type.
static bool IsExpected(const BaseStation *st)
Helper for checking whether the given station is of this type.
static Pool::IterateWrapper< Station > Iterate(size_t from=0)
Returns an iterable ensemble of all valid stations of type T.
static Waypoint * Get(auto index)
Gets station with given index.
static Waypoint * From(BaseStation *st)
Converts a BaseStation to SpecializedStation with type checking.
uint8_t station_spread
amount a station may spread
bool distant_join_stations
allow to join non-adjacent stations
A row being displayed in the cargo view (as opposed to being "hidden" behind a plus sign).
StationID next_station
ID of the station belonging to the entry actually displayed if it's to/from/via.
CargoDataEntry * filter
Parent of the cargo entry belonging to the row.
CargoType next_cargo
ID of the cargo belonging to the entry actually displayed if it's cargo.
The StationView window.
void EstimateDestinations(CargoType cargo, StationID source, StationID next, uint count, CargoDataEntry *dest)
Estimate the amounts of cargo per final destination for a given cargo, source station and next hop an...
void HandleCargoWaitingClick(int row)
Handle a click on a specific row in the cargo view.
void OnQueryTextFinished(std::optional< std::string > str) override
The query window opened from this window has closed.
Mode
Display mode of the cargo view.
@ MODE_PLANNED
Show cargo planned to pass through the station.
@ MODE_WAITING
Show cargo waiting at the station.
std::array< Grouping, NUM_COLUMNS > groupings
Grouping modes for the different columns.
void SelectSortBy(int index)
Select a new sort criterium for the cargo view.
void OnResize() override
Called after the window got resized.
void Close(int data=0) override
Hide the window and all its child windows, and mark them for a later deletion.
Grouping
Type of grouping used in each of the "columns".
@ GR_SOURCE
Group by source of cargo ("from").
@ GR_NEXT
Group by next station ("via").
@ GR_CARGO
Group by cargo type.
@ GR_DESTINATION
Group by estimated final destination ("to").
void RecalcDestinations(CargoType i)
Rebuild the cache for estimated destinations which is used to quickly show the "destination" entries ...
int DrawCargoRatings(const Rect &r) const
Draw cargo ratings in the WID_SV_ACCEPT_RATING_LIST widget.
static const int NUM_COLUMNS
Number of "columns" in the cargo view: cargo, from, via, to.
void SetDisplayedRow(const CargoDataEntry *data)
Mark a specific row, characterized by its CargoDataEntry, as expanded.
StringID SearchNonStop(CargoDataEntry *cd, StationID station, int column)
Determine if we need to show the special "non-stop" string.
void BuildCargoList(CargoType i, const StationCargoList &packets, CargoDataEntry *cargo)
Build up the cargo view for WAITING mode and a specific cargo.
Invalidation
Type of data invalidation.
@ INV_FLOWS
The planned flows have been recalculated and everything has to be updated.
@ INV_CARGO
Some cargo has been added or removed.
void SelectSortOrder(SortOrder order)
Select a new sort order for the cargo view.
CargoDataEntry expanded_rows
Parent entry of currently expanded rows.
void OnPaint() override
The window must be repainted.
static constexpr uint RATING_LINES
Height in lines of the cargo ratings view.
CargoDataVector displayed_rows
Parent entry of currently displayed rows (including collapsed ones).
static const StringID group_names[]
Names of the grouping options in the dropdown.
int grouping_index
Currently selected entry in the grouping drop down.
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
static constexpr uint ACCEPTS_LINES
Height in lines of the accepted cargo view.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
StringID GetEntryString(StationID station, StringID here, StringID other_station, StringID any) const
Select the correct string for an entry referring to the specified station.
void BuildFlowList(CargoType i, const FlowStatMap &flows, CargoDataEntry *cargo)
Build up the cargo view for PLANNED mode and a specific cargo.
std::array< CargoSortType, NUM_COLUMNS > sortings
Sort types of the different 'columns'.
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
Get the raw string for a widget.
void BuildCargoList(CargoDataEntry *cargo, const Station *st)
Build up the cargo view for all cargoes.
std::array< SortOrder, NUM_COLUMNS > sort_orders
Sort order (ascending/descending) for the 'columns'.
uint expand_shrink_width
The width allocated to the expand/shrink 'button'.
int DrawEntries(CargoDataEntry *entry, const Rect &r, int pos, int maxrows, int column, CargoType cargo=INVALID_CARGO)
Draw the given cargo entries in the station GUI.
void HandleCargoWaitingClick(CargoDataEntry *filter, Tid next)
Expand or collapse a specific row.
int rating_lines
Number of lines in the cargo ratings view.
static const StringID sort_names[]
Names of the sorting options in the dropdown.
CargoDataEntry cached_destinations
Cache for the flows passing through this station.
int scroll_to_row
If set, scroll the main viewport to the station pointed to by this row.
Mode current_mode
Currently selected display mode of cargo view.
int accepts_lines
Number of lines in the accepted cargo view.
void SelectGroupBy(int index)
Select a new grouping mode for the cargo view.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
void OnDropdownSelect(WidgetID widget, int index) override
A dropdown option associated to this window has been selected.
int DrawAcceptedCargo(const Rect &r) const
Draw accepted cargo in the WID_SV_ACCEPT_RATING_LIST widget.
void ShowCargo(CargoDataEntry *data, CargoType cargo, StationID source, StationID next, StationID dest, uint count)
Show a certain cargo entry characterized by source/next/dest station, cargo type and amount of cargo ...
Station data structure.
std::array< GoodsEntry, NUM_CARGO > goods
Goods at this station.
Airport airport
Tile area the airport covers.
Struct containing TileIndex and StationID.
TileIndex tile
TileIndex.
StationID station
StationID.
HighLightStyle drawstyle
Lower bits 0-3 are reserved for detailed highlight information.
uint8_t dirty
Whether the build station window needs to redraw due to the changed selection.
Point size
Size, in tile "units", of the white/red selection area.
Point pos
Location, in tile "units", of the northern tile of the selected area.
bool freeze
Freeze highlight in place.
CompanyID exclusivity
which company has exclusivity
Definition town.h:73
uint8_t exclusive_counter
months till the exclusivity expires
Definition town.h:74
The information about a vehicle list.
Definition vehiclelist.h:32
Representation of a waypoint.
High level window description.
Definition window_gui.h:168
Number to differentiate different windows of the same class.
Data structure for an opened window.
Definition window_gui.h:274
void ReInit(int rx=0, int ry=0, bool reposition=false)
Re-initialize a window, and optionally change its size.
Definition window.cpp:955
virtual void Close(int data=0)
Hide the window and all its child windows, and mark them for a later deletion.
Definition window.cpp:1050
static int SortButtonWidth()
Get width of up/down arrow of sort button state.
Definition widget.cpp:793
void FinishInitNested(WindowNumber window_number=0)
Perform the second part of the initialization of a nested widget tree.
Definition window.cpp:1736
void DrawWidgets() const
Paint all widgets of a window.
Definition widget.cpp:744
Window * parent
Parent window.
Definition window_gui.h:329
void RaiseWidget(WidgetID widget_index)
Marks a widget as raised.
Definition window_gui.h:470
void SetWidgetDirty(WidgetID widget_index) const
Invalidate a widget, i.e.
Definition window.cpp:554
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:502
void DrawSortButtonState(WidgetID widget, SortButtonState state) const
Draw a sort button's up or down arrow symbol.
Definition widget.cpp:776
void CloseChildWindows(WindowClass wc=WC_INVALID) const
Close all children a window might have in a head-recursive manner.
Definition window.cpp:1038
ResizeInfo resize
Resize information.
Definition window_gui.h:315
void CreateNestedTree()
Perform the first part of the initialization of a nested widget tree.
Definition window.cpp:1726
bool IsWidgetLowered(WidgetID widget_index) const
Gets the lowered state of a widget.
Definition window_gui.h:492
Owner owner
The owner of the content shown in this window. Company colour is acquired from this variable.
Definition window_gui.h:317
void SetWidgetLoweredState(WidgetID widget_index, bool lowered_stat)
Sets the lowered/raised status of a widget.
Definition window_gui.h:442
bool IsShaded() const
Is window shaded currently?
Definition window_gui.h:558
void SetTimeout()
Set the timeout flag of the window and initiate the timer.
Definition window_gui.h:356
const NWID * GetWidget(WidgetID widnum) const
Get the nested widget with number widnum from the nested widget tree.
Definition window_gui.h:973
void LowerWidget(WidgetID widget_index)
Marks a widget as lowered.
Definition window_gui.h:461
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition window.cpp:311
void SetWidgetDisabledState(WidgetID widget_index, bool disab_stat)
Sets the enabled/disabled status of a widget.
Definition window_gui.h:382
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:312
void ToggleWidgetLoweredState(WidgetID widget_index)
Invert the lowered/raised status of a widget.
Definition window_gui.h:451
WindowNumber window_number
Window number within the window class.
Definition window_gui.h:303
Stuff related to the text buffer GUI.
@ EnableDefault
enable the 'Default' button ("\0" is returned)
@ LengthIsInChars
the length of the string is counted in characters
Owner GetTileOwner(Tile tile)
Returns the owner of a tile.
Definition tile_map.h:178
static debug_inline bool IsTileType(Tile tile, TileType type)
Checks if a tile is a given tiletype.
Definition tile_map.h:150
static const uint TILE_SIZE
Tile size in world coordinates.
Definition tile_type.h:15
constexpr TileIndex INVALID_TILE
The very nice invalid tile marker.
Definition tile_type.h:95
@ MP_STATION
A tile of a station.
Definition tile_type.h:53
Functions related to tile highlights.
void ResetObjectToPlace()
Reset the cursor and mouse mode handling back to default (normal cursor, only clicking in windows).
void UpdateTileSelection()
Updates tile highlighting for all cases.
@ HT_RECT
rectangle (stations, depots, ...)
Base of the town class.
Functions related to the vehicle's GUIs.
VehicleType
Available vehicle types.
@ VEH_ROAD
Road vehicle type.
@ VEH_AIRCRAFT
Aircraft vehicle type.
@ VEH_SHIP
Ship vehicle type.
@ VEH_TRAIN
Train vehicle type.
Functions and type for generating vehicle lists.
@ VL_STATION_LIST
Index is the station.
Definition vehiclelist.h:25
bool ScrollMainWindowToTile(TileIndex tile, bool instant)
Scrolls the viewport of the main window to a given location.
void SetViewportCatchmentStation(const Station *st, bool sel)
Select or deselect station for coverage area highlight.
const Station * _viewport_highlight_station
Currently selected station for coverage area highlight.
Functions related to (drawing on) viewports.
Base of waypoints.
@ WPF_ROAD
This is a road waypoint.
static RectPadding ScaleGUITrad(const RectPadding &r)
Scale a RectPadding to GUI zoom level.
Definition widget.cpp:48
@ WWT_PUSHTXTBTN
Normal push-button (no toggle button) with text caption.
@ WWT_PUSHIMGBTN
Normal push-button (no toggle button) with image caption.
@ NWID_HORIZONTAL
Horizontal container.
Definition widget_type.h:65
@ WWT_TEXTBTN
(Toggle) Button with text
Definition widget_type.h:45
@ WWT_PANEL
Simple depressed panel.
Definition widget_type.h:40
@ WWT_STICKYBOX
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX)
Definition widget_type.h:56
@ WWT_SHADEBOX
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX)
Definition widget_type.h:54
@ WWT_CAPTION
Window caption (window title between closebox and stickybox)
Definition widget_type.h:51
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition widget_type.h:75
@ NWID_VERTICAL
Vertical container.
Definition widget_type.h:67
@ WWT_CLOSEBOX
Close box (at top-left of a window)
Definition widget_type.h:59
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window)
Definition widget_type.h:58
@ WWT_DEFSIZEBOX
Default window size box (at top-right of a window, between WWT_SHADEBOX and WWT_STICKYBOX)
Definition widget_type.h:55
@ WWT_DROPDOWN
Drop down list.
Definition widget_type.h:60
@ EqualSize
Containers should keep all their (resizing) children equally large.
void CloseWindowById(WindowClass cls, WindowNumber number, bool force, int data)
Close a window by its class and window number (if it is open).
Definition window.cpp:1143
Window * FindWindowById(WindowClass cls, WindowNumber number)
Find a window by its class and window number.
Definition window.cpp:1101
Window functions not directly related to making/drawing windows.
@ Construction
This window is used for construction; close it whenever changing company.
@ SBS_DOWN
Sort ascending.
Definition window_gui.h:219
@ SBS_UP
Sort descending.
Definition window_gui.h:220
@ WDP_AUTO
Find a place automatically.
Definition window_gui.h:145
int WidgetID
Widget ID.
Definition window_type.h:20
@ WC_STATION_LIST
Station list; Window numbers:
@ WC_ROADVEH_LIST
Road vehicle list; Window numbers:
@ WC_NONE
No window, redirects to WC_MAIN_WINDOW.
Definition window_type.h:47
@ WC_SELECT_STATION
Select station (when joining stations); Window numbers:
@ WC_SHIPS_LIST
Ships list; Window numbers:
@ WC_STATION_VIEW
Station view; Window numbers:
@ WC_TRAINS_LIST
Trains list; Window numbers:
@ WC_DROPDOWN_MENU
Drop down menu; Window numbers:
@ WC_AIRCRAFT_LIST
Aircraft list; Window numbers:
Functions related to zooming.
int ScaleSpriteTrad(int value)
Scale traditional pixel dimensions to GUI zoom level, for drawing sprites.
Definition zoom_func.h:107