diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -550,18 +550,18 @@ struct RefitOption { } }; -typedef std::vector SubtypeList; ///< List of refit subtypes associated to a cargo. +using RefitOptions = std::map, CargoIDComparator>; ///< Available refit options (subtype and string) associated with each cargo type. /** * Draw the list of available refit options for a consist and highlight the selected refit option (if any). - * @param list List of subtype options for each (sorted) cargo. - * @param sel Selected refit cargo-type in the window + * @param refits Available refit options for each (sorted) cargo. + * @param sel Selected refit option in the window * @param pos Position of the selected item in caller widow * @param rows Number of rows(capacity) in caller window * @param delta Step height in caller window * @param r Rectangle of the matrix widget. */ -static void DrawVehicleRefitWindow(const SubtypeList list[NUM_CARGO], const int sel[2], uint pos, uint rows, uint delta, const Rect &r) +static void DrawVehicleRefitWindow(const RefitOptions &refits, const RefitOption *sel, uint pos, uint rows, uint delta, const Rect &r) { Rect ir = r.Shrink(WidgetDimensions::scaled.matrix); uint current = 0; @@ -578,12 +578,13 @@ static void DrawVehicleRefitWindow(const Rect tr = ir.Indent(iconwidth + WidgetDimensions::scaled.hsep_wide, rtl); /* Draw the list of subtypes for each cargo, and find the selected refit option (by its position). */ - for (uint i = 0; current < pos + rows && i < NUM_CARGO; i++) { - for (uint j = 0; current < pos + rows && j < list[i].size(); j++) { - const RefitOption &refit = list[i][j]; - - /* Hide subtypes if sel[0] does not match */ - if (sel[0] != (int)i && refit.subtype != 0xFF) continue; + for (const auto &pair : refits) { + bool has_subtypes = pair.second.size() > 1; + for (const RefitOption &refit : pair.second) { + if (current >= pos + rows) break; + + /* Hide subtypes if selected cargo type does not match */ + if ((sel == nullptr || sel->cargo != refit.cargo) && refit.subtype != UINT8_MAX) continue; /* Refit options with a position smaller than pos don't have to be drawn. */ if (current < pos) { @@ -591,19 +592,19 @@ static void DrawVehicleRefitWindow(const continue; } - if (list[i].size() > 1) { - if (refit.subtype != 0xFF) { + if (has_subtypes) { + if (refit.subtype != UINT8_MAX) { /* Draw tree lines */ int ycenter = tr.top + FONT_HEIGHT_NORMAL / 2; - GfxDrawLine(iconcenter, tr.top - WidgetDimensions::scaled.matrix.top, iconcenter, j == list[i].size() - 1 ? ycenter : tr.top - WidgetDimensions::scaled.matrix.top + delta - 1, linecolour); + GfxDrawLine(iconcenter, tr.top - WidgetDimensions::scaled.matrix.top, iconcenter, (&refit == &pair.second.back()) ? ycenter : tr.top - WidgetDimensions::scaled.matrix.top + delta - 1, linecolour); GfxDrawLine(iconcenter, ycenter, iconinner, ycenter, linecolour); } else { /* Draw expand/collapse icon */ - DrawSprite(sel[0] == (int)i ? SPR_CIRCLE_UNFOLDED : SPR_CIRCLE_FOLDED, PAL_NONE, iconleft, tr.top + (FONT_HEIGHT_NORMAL - iconheight) / 2); + DrawSprite((sel != nullptr && sel->cargo == refit.cargo) ? SPR_CIRCLE_UNFOLDED : SPR_CIRCLE_FOLDED, PAL_NONE, iconleft, tr.top + (FONT_HEIGHT_NORMAL - iconheight) / 2); } } - TextColour colour = (sel[0] == (int)i && (uint)sel[1] == j) ? TC_WHITE : TC_BLACK; + TextColour colour = (sel != nullptr && sel->cargo == refit.cargo && sel->subtype == refit.subtype) ? TC_WHITE : TC_BLACK; /* Get the cargo name. */ SetDParam(0, CargoSpec::Get(refit.cargo)->name); SetDParam(1, refit.string); @@ -617,9 +618,8 @@ static void DrawVehicleRefitWindow(const /** Refit cargo window. */ struct RefitWindow : public Window { - int sel[2]; ///< Index in refit options, sel[0] == -1 if nothing is selected. - RefitOption *cargo; ///< Refit option selected by #sel. - SubtypeList list[NUM_CARGO]; ///< List of refit subtypes available for each sorted cargo. + const RefitOption *selected_refit; ///< Selected refit option. + RefitOptions refit_list; ///< List of refit subtypes available for each sorted cargo. VehicleOrderID order; ///< If not #INVALID_VEH_ORDER_ID, selection is part of a refit order (rather than execute directly). uint information_width; ///< Width required for correctly displaying all cargoes in the information panel. Scrollbar *vscroll; ///< The main scrollbar. @@ -638,7 +638,12 @@ struct RefitWindow : public Window { */ void BuildRefitList() { - for (uint i = 0; i < NUM_CARGO; i++) this->list[i].clear(); + /* Store the currently selected RefitOption. */ + std::optional current_refit_option; + if (this->selected_refit != nullptr) current_refit_option = *(this->selected_refit); + this->selected_refit = nullptr; + + this->refit_list.clear(); Vehicle *v = Vehicle::Get(this->window_number); /* Check only the selected vehicles. */ @@ -657,19 +662,16 @@ struct RefitWindow : public Window { if (this->auto_refit && !HasBit(e->info.misc_flags, EF_AUTO_REFIT)) continue; /* Loop through all cargoes in the refit mask */ - int current_index = 0; for (const auto &cs : _sorted_cargo_specs) { CargoID cid = cs->Index(); /* Skip cargo type if it's not listed */ - if (!HasBit(cmask, cid)) { - current_index++; - continue; - } - - bool first_vehicle = this->list[current_index].size() == 0; + if (!HasBit(cmask, cid)) continue; + + auto &list = this->refit_list[cid]; + bool first_vehicle = list.size() == 0; if (first_vehicle) { /* Keeping the current subtype is always an option. It also serves as the option in case of no subtypes */ - this->list[current_index].push_back({cid, 0xFF, STR_EMPTY}); + list.push_back({cid, UINT8_MAX, STR_EMPTY}); } /* Check the vehicle's callback mask for cargo suffixes. @@ -701,16 +703,15 @@ struct RefitWindow : public Window { option.cargo = cid; option.subtype = refit_cyc; option.string = subtype; - include(this->list[current_index], option); + include(list, option); } else { /* Intersect the subtypes of earlier vehicles with the subtypes of this vehicle */ if (subtype == STR_EMPTY) { /* No more subtypes for this vehicle, delete all subtypes >= refit_cyc */ - SubtypeList &l = this->list[current_index]; - /* 0xFF item is in front, other subtypes are sorted. So just truncate the list in the right spot */ - for (uint i = 1; i < l.size(); i++) { - if (l[i].subtype >= refit_cyc) { - l.resize(i); + /* UINT8_MAX item is in front, other subtypes are sorted. So just truncate the list in the right spot */ + for (uint i = 1; i < list.size(); i++) { + if (list[i].subtype >= refit_cyc) { + list.resize(i); break; } } @@ -718,11 +719,10 @@ struct RefitWindow : public Window { } else { /* Check whether the subtype matches with the subtype of earlier vehicles. */ uint pos = 1; - SubtypeList &l = this->list[current_index]; - while (pos < l.size() && l[pos].subtype != refit_cyc) pos++; - if (pos < l.size() && l[pos].string != subtype) { + while (pos < list.size() && list[pos].subtype != refit_cyc) pos++; + if (pos < list.size() && list[pos].string != subtype) { /* String mismatch, remove item keeping the order */ - l.erase(l.begin() + pos); + list.erase(list.begin() + pos); } } } @@ -736,9 +736,23 @@ struct RefitWindow : public Window { v->First()->InvalidateNewGRFCache(); v->InvalidateNewGRFCache(); } - current_index++; } } while (v->IsGroundVehicle() && (v = v->Next()) != nullptr); + + /* Restore the previously selected RefitOption. */ + if (current_refit_option.has_value()) { + for (const auto &pair : this->refit_list) { + for (const auto &refit : pair.second) { + if (refit.cargo == current_refit_option->cargo && refit.subtype == current_refit_option->subtype) { + this->selected_refit = &refit; + break; + } + } + if (this->selected_refit != nullptr) break; + } + } + + this->SetWidgetDisabledState(WID_VR_REFIT, this->selected_refit == nullptr); } /** @@ -746,24 +760,22 @@ struct RefitWindow : public Window { */ void RefreshScrollbar() { - uint scroll_row = 0; - uint row = 0; - - for (uint i = 0; i < NUM_CARGO; i++) { - for (uint j = 0; j < this->list[i].size(); j++) { - const RefitOption &refit = this->list[i][j]; - - /* Hide subtypes if sel[0] does not match */ - if (this->sel[0] != (int)i && refit.subtype != 0xFF) continue; - - if (this->sel[0] == (int)i && (uint)this->sel[1] == j) scroll_row = row; - - row++; + size_t scroll_row = 0; + size_t rows = 0; + CargoID cargo = this->selected_refit == nullptr ? (CargoID)CT_INVALID : this->selected_refit->cargo; + + for (const auto &pair : this->refit_list) { + if (pair.first == cargo) { + /* selected_refit points to an element in the vector so no need to search for it. */ + scroll_row = rows + (this->selected_refit - pair.second.data()); + rows += pair.second.size(); + } else { + rows++; /* Unselected cargo type is collapsed into one row. */ } } - this->vscroll->SetCount(row); - if (scroll_row < row) this->vscroll->ScrollTowards(scroll_row); + this->vscroll->SetCount(rows); + this->vscroll->ScrollTowards(static_cast(scroll_row)); } /** @@ -774,45 +786,24 @@ struct RefitWindow : public Window { { uint row = 0; - for (uint i = 0; i < NUM_CARGO; i++) { - for (uint j = 0; j < this->list[i].size(); j++) { - const RefitOption &refit = this->list[i][j]; - - /* Hide subtypes if sel[0] does not match */ - if (this->sel[0] != (int)i && refit.subtype != 0xFF) continue; - + for (const auto &pair : refit_list) { + for (const RefitOption &refit : pair.second) { if (row == click_row) { - this->sel[0] = i; - this->sel[1] = j; + this->selected_refit = &refit; return; } - row++; + /* If this cargo type is not already selected then its subtypes are not visible, so skip the rest. */ + if (this->selected_refit == nullptr || this->selected_refit->cargo != refit.cargo) break; } } - this->sel[0] = -1; - this->sel[1] = 0; - } - - /** - * Gets the #RefitOption placed in the selected index. - * @return Pointer to the #RefitOption currently in use. - */ - RefitOption *GetRefitOption() - { - if (this->sel[0] < 0) return nullptr; - - SubtypeList &l = this->list[this->sel[0]]; - if ((uint)this->sel[1] >= l.size()) return nullptr; - - return &l[this->sel[1]]; + /* No selection made */ + this->selected_refit = nullptr; } RefitWindow(WindowDesc *desc, const Vehicle *v, VehicleOrderID order, bool auto_refit) : Window(desc) { - this->sel[0] = -1; - this->sel[1] = 0; this->auto_refit = auto_refit; this->order = order; this->CreateNestedTree(); @@ -830,37 +821,13 @@ struct RefitWindow : public Window { this->FinishInitNested(v->index); this->owner = v->owner; - this->SetWidgetDisabledState(WID_VR_REFIT, this->sel[0] < 0); + this->SetWidgetDisabledState(WID_VR_REFIT, this->selected_refit == nullptr); } void OnInit() override { - if (this->cargo != nullptr) { - /* Store the RefitOption currently in use. */ - RefitOption current_refit_option = *(this->cargo); - - /* Rebuild the refit list */ - this->BuildRefitList(); - this->sel[0] = -1; - this->sel[1] = 0; - this->cargo = nullptr; - for (uint i = 0; this->cargo == nullptr && i < NUM_CARGO; i++) { - for (uint j = 0; j < list[i].size(); j++) { - if (list[i][j] == current_refit_option) { - this->sel[0] = i; - this->sel[1] = j; - this->cargo = &list[i][j]; - break; - } - } - } - - this->SetWidgetDisabledState(WID_VR_REFIT, this->sel[0] < 0); - this->RefreshScrollbar(); - } else { - /* Rebuild the refit list */ - this->OnInvalidateData(VIWD_CONSIST_CHANGED); - } + /* (Re)build the refit list */ + this->OnInvalidateData(VIWD_CONSIST_CHANGED); } void OnPaint() override @@ -913,14 +880,14 @@ struct RefitWindow : public Window { * @return INVALID_STRING_ID if there is no capacity. StringID to use in any other case. * @post String parameters have been set. */ - StringID GetCapacityString(RefitOption *option) const + StringID GetCapacityString(const RefitOption &option) const { assert(_current_company == _local_company); - auto [cost, refit_capacity, mail_capacity, cargo_capacities] = Command::Do(DC_QUERY_COST, this->selected_vehicle, option->cargo, option->subtype, this->auto_refit, false, this->num_vehicles); + auto [cost, refit_capacity, mail_capacity, cargo_capacities] = Command::Do(DC_QUERY_COST, this->selected_vehicle, option.cargo, option.subtype, this->auto_refit, false, this->num_vehicles); if (cost.Failed()) return INVALID_STRING_ID; - SetDParam(0, option->cargo); + SetDParam(0, option.cargo); SetDParam(1, refit_capacity); Money money = cost.GetCost(); @@ -1021,12 +988,12 @@ struct RefitWindow : public Window { } case WID_VR_MATRIX: - DrawVehicleRefitWindow(this->list, this->sel, this->vscroll->GetPosition(), this->vscroll->GetCapacity(), this->resize.step_height, r); + DrawVehicleRefitWindow(this->refit_list, this->selected_refit, this->vscroll->GetPosition(), this->vscroll->GetCapacity(), this->resize.step_height, r); break; case WID_VR_INFO: - if (this->cargo != nullptr) { - StringID string = this->GetCapacityString(this->cargo); + if (this->selected_refit != nullptr) { + StringID string = this->GetCapacityString(*this->selected_refit); if (string != INVALID_STRING_ID) { DrawStringMultiLine(r.Shrink(WidgetDimensions::scaled.framerect), string); } @@ -1061,9 +1028,9 @@ struct RefitWindow : public Window { uint max_width = 0; /* Check the width of all cargo information strings. */ - for (uint i = 0; i < NUM_CARGO; i++) { - for (uint j = 0; j < this->list[i].size(); j++) { - StringID string = this->GetCapacityString(&list[i][j]); + for (const auto &list : this->refit_list) { + for (const RefitOption &refit : list.second) { + StringID string = this->GetCapacityString(refit); if (string != INVALID_STRING_ID) { Dimension dim = GetStringBoundingBox(string); max_width = std::max(dim.width, max_width); @@ -1080,7 +1047,6 @@ struct RefitWindow : public Window { case 1: // A new cargo has been selected. if (!gui_scope) break; - this->cargo = GetRefitOption(); this->RefreshScrollbar(); break; } @@ -1168,7 +1134,7 @@ struct RefitWindow : public Window { case WID_VR_MATRIX: { // listbox this->SetSelection(this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_VR_MATRIX)); - this->SetWidgetDisabledState(WID_VR_REFIT, this->sel[0] < 0); + this->SetWidgetDisabledState(WID_VR_REFIT, this->selected_refit == nullptr); this->InvalidateData(1); if (click_count == 1) break; @@ -1176,14 +1142,14 @@ struct RefitWindow : public Window { } case WID_VR_REFIT: // refit button - if (this->cargo != nullptr) { + if (this->selected_refit != nullptr) { const Vehicle *v = Vehicle::Get(this->window_number); if (this->order == INVALID_VEH_ORDER_ID) { bool delete_window = this->selected_vehicle == v->index && this->num_vehicles == UINT8_MAX; - if (Command::Post(GetCmdRefitVehMsg(v), v->tile, this->selected_vehicle, this->cargo->cargo, this->cargo->subtype, false, false, this->num_vehicles) && delete_window) this->Close(); + if (Command::Post(GetCmdRefitVehMsg(v), v->tile, this->selected_vehicle, this->selected_refit->cargo, this->selected_refit->subtype, false, false, this->num_vehicles) && delete_window) this->Close(); } else { - if (Command::Post(v->tile, v->index, this->order, this->cargo->cargo)) this->Close(); + if (Command::Post(v->tile, v->index, this->order, this->selected_refit->cargo)) this->Close(); } } break;