Changeset - r28823:ebda70ff18b6
[Not reviewed]
master
0 2 0
Peter Nelson - 9 months ago 2024-02-25 21:33:11
peter1138@openttd.org
Codefix: DrawEngineList does not accept EngineID.

Replace min/max parameters of DrawEngineList with scrollbar reference, and use iterators instead of indices.
2 files changed with 12 insertions and 17 deletions:
0 comments (0 inline, 0 general)
src/autoreplace_gui.cpp
Show inline comments
 
@@ -12,49 +12,49 @@
 
#include "vehicle_gui.h"
 
#include "newgrf_engine.h"
 
#include "rail.h"
 
#include "road.h"
 
#include "strings_func.h"
 
#include "window_func.h"
 
#include "autoreplace_func.h"
 
#include "company_func.h"
 
#include "engine_base.h"
 
#include "window_gui.h"
 
#include "engine_gui.h"
 
#include "settings_func.h"
 
#include "core/geometry_func.hpp"
 
#include "rail_gui.h"
 
#include "road_gui.h"
 
#include "widgets/dropdown_func.h"
 
#include "autoreplace_cmd.h"
 
#include "group_cmd.h"
 
#include "settings_cmd.h"
 

	
 
#include "widgets/autoreplace_widget.h"
 

	
 
#include "safeguards.h"
 

	
 
void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_list, uint16_t min, uint16_t max, EngineID selected_id, bool show_count, GroupID selected_group);
 
void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_list, const Scrollbar &sb, EngineID selected_id, bool show_count, GroupID selected_group);
 

	
 
static bool EngineNumberSorter(const GUIEngineListItem &a, const GUIEngineListItem &b)
 
{
 
	return Engine::Get(a.engine_id)->list_position < Engine::Get(b.engine_id)->list_position;
 
}
 

	
 
/**
 
 * Rebuild the left autoreplace list if an engine is removed or added
 
 * @param e Engine to check if it is removed or added
 
 * @param id_g The group the engine belongs to
 
 *  Note: this function only works if it is called either
 
 *   - when a new vehicle is build, but before it's counted in num_engines
 
 *   - when a vehicle is deleted and after it's subtracted from num_engines
 
 *   - when not changing the count (used when changing replace orders)
 
 */
 
void InvalidateAutoreplaceWindow(EngineID e, GroupID id_g)
 
{
 
	if (GetGroupNumEngines(_local_company, id_g, e) == 0 || GetGroupNumEngines(_local_company, ALL_GROUP, e) == 0) {
 
		/* We don't have any of this engine type.
 
		 * Either we just sold the last one, we build a new one or we stopped replacing it.
 
		 * In all cases, we need to update the left list */
 
		InvalidateWindowData(WC_REPLACE_VEHICLE, Engine::Get(e)->type, 1);
 
	}
 
}
 
@@ -466,53 +466,51 @@ public:
 

	
 
			case WID_RV_INFO_TAB: {
 
				const Company *c = Company::Get(_local_company);
 
				StringID str;
 
				if (this->sel_engine[0] != INVALID_ENGINE) {
 
					if (!EngineHasReplacementForCompany(c, this->sel_engine[0], this->sel_group)) {
 
						str = STR_REPLACE_NOT_REPLACING;
 
					} else {
 
						bool when_old = false;
 
						EngineID e = EngineReplacementForCompany(c, this->sel_engine[0], this->sel_group, &when_old);
 
						str = when_old ? STR_REPLACE_REPLACING_WHEN_OLD : STR_ENGINE_NAME;
 
						SetDParam(0, PackEngineNameDParam(e, EngineNameContext::PurchaseList));
 
					}
 
				} else {
 
					str = STR_REPLACE_NOT_REPLACING_VEHICLE_SELECTED;
 
				}
 

	
 
				DrawString(r.Shrink(WidgetDimensions::scaled.frametext, WidgetDimensions::scaled.framerect), str, TC_BLACK, SA_HOR_CENTER);
 
				break;
 
			}
 

	
 
			case WID_RV_LEFT_MATRIX:
 
			case WID_RV_RIGHT_MATRIX: {
 
				int side = (widget == WID_RV_LEFT_MATRIX) ? 0 : 1;
 
				EngineID start  = static_cast<EngineID>(this->vscroll[side]->GetPosition()); // what is the offset for the start (scrolling)
 
				EngineID end    = static_cast<EngineID>(std::min<size_t>(this->vscroll[side]->GetCapacity() + start, this->engines[side].size()));
 

	
 
				/* Do the actual drawing */
 
				DrawEngineList((VehicleType)this->window_number, r, this->engines[side], start, end, this->sel_engine[side], side == 0, this->sel_group);
 
				DrawEngineList((VehicleType)this->window_number, r, this->engines[side], *this->vscroll[side], this->sel_engine[side], side == 0, this->sel_group);
 
				break;
 
			}
 
		}
 
	}
 

	
 
	void OnPaint() override
 
	{
 
		if (this->engines[0].NeedRebuild() || this->engines[1].NeedRebuild()) this->GenerateLists();
 

	
 
		Company *c = Company::Get(_local_company);
 

	
 
		/* Disable the "Start Replacing" button if:
 
		 *    Either engines list is empty
 
		 * or The selected replacement engine has a replacement (to prevent loops). */
 
		this->SetWidgetDisabledState(WID_RV_START_REPLACE,
 
				this->sel_engine[0] == INVALID_ENGINE || this->sel_engine[1] == INVALID_ENGINE || EngineReplacementForCompany(c, this->sel_engine[1], this->sel_group) != INVALID_ENGINE);
 

	
 
		/* Disable the "Stop Replacing" button if:
 
		 *   The left engines list (existing vehicle) is empty
 
		 *   or The selected vehicle has no replacement set up */
 
		this->SetWidgetDisabledState(WID_RV_STOP_REPLACE, this->sel_engine[0] == INVALID_ENGINE || !EngineHasReplacementForCompany(c, this->sel_engine[0], this->sel_group));
 

	
 
		this->DrawWidgets();
 

	
src/build_vehicle_gui.cpp
Show inline comments
 
@@ -977,139 +977,137 @@ int DrawVehiclePurchaseInfo(int left, in
 
		y += GetCharacterHeight(FS_NORMAL);
 
	}
 

	
 
	if (refittable) y = ShowRefitOptionsList(left, right, y, engine_number);
 

	
 
	/* Additional text from NewGRF */
 
	y = ShowAdditionalText(left, right, y, engine_number);
 

	
 
	/* The NewGRF's name which the vehicle comes from */
 
	const GRFConfig *config = GetGRFConfig(e->GetGRFID());
 
	if (_settings_client.gui.show_newgrf_name && config != nullptr)
 
	{
 
		DrawString(left, right, y, config->GetName(), TC_BLACK);
 
		y += GetCharacterHeight(FS_NORMAL);
 
	}
 

	
 
	return y;
 
}
 

	
 
/**
 
 * Engine drawing loop
 
 * @param type Type of vehicle (VEH_*)
 
 * @param r The Rect of the list
 
 * @param eng_list What engines to draw
 
 * @param min where to start in the list
 
 * @param max where in the list to end
 
 * @param sb Scrollbar of list.
 
 * @param selected_id what engine to highlight as selected, if any
 
 * @param show_count Whether to show the amount of engines or not
 
 * @param selected_group the group to list the engines of
 
 */
 
void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_list, uint16_t min, uint16_t max, EngineID selected_id, bool show_count, GroupID selected_group)
 
void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_list, const Scrollbar &sb, EngineID selected_id, bool show_count, GroupID selected_group)
 
{
 
	static const int sprite_y_offsets[] = { -1, -1, -2, -2 };
 

	
 
	/* Obligatory sanity checks! */
 
	assert(max <= eng_list.size());
 
	auto [first, last] = sb.GetVisibleRangeIterators(eng_list);
 

	
 
	bool rtl = _current_text_dir == TD_RTL;
 
	int step_size = GetEngineListHeight(type);
 
	int sprite_left  = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_left;
 
	int sprite_right = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_right;
 
	int sprite_width = sprite_left + sprite_right;
 
	int circle_width = std::max(GetScaledSpriteSize(SPR_CIRCLE_FOLDED).width, GetScaledSpriteSize(SPR_CIRCLE_UNFOLDED).width);
 
	int linecolour = GetColourGradient(COLOUR_ORANGE, SHADE_NORMAL);
 

	
 
	Rect ir      = r.WithHeight(step_size).Shrink(WidgetDimensions::scaled.matrix);
 
	int sprite_y_offset = ScaleSpriteTrad(sprite_y_offsets[type]) + ir.Height() / 2;
 

	
 
	Dimension replace_icon = {0, 0};
 
	int count_width = 0;
 
	if (show_count) {
 
		replace_icon = GetSpriteSize(SPR_GROUP_REPLACE_ACTIVE);
 

	
 
		uint biggest_num_engines = 0;
 
		for (auto i = min; i < max; i++) {
 
			const auto &item = eng_list[i];
 
			const uint num_engines = GetGroupNumEngines(_local_company, selected_group, item.engine_id);
 
		for (auto it = first; it != last; ++it) {
 
			const uint num_engines = GetGroupNumEngines(_local_company, selected_group, it->engine_id);
 
			biggest_num_engines = std::max(biggest_num_engines, num_engines);
 
		}
 

	
 
		SetDParam(0, biggest_num_engines);
 
		count_width = GetStringBoundingBox(STR_JUST_COMMA, FS_SMALL).width;
 
	}
 

	
 
	Rect tr = ir.Indent(circle_width + WidgetDimensions::scaled.hsep_normal + sprite_width + WidgetDimensions::scaled.hsep_wide, rtl); // Name position
 
	Rect cr = tr.Indent(replace_icon.width + WidgetDimensions::scaled.hsep_wide, !rtl).WithWidth(count_width, !rtl);  // Count position
 
	Rect rr = tr.WithWidth(replace_icon.width, !rtl);                                                                 // Replace icon position
 
	if (show_count) tr = tr.Indent(count_width + WidgetDimensions::scaled.hsep_normal + replace_icon.width + WidgetDimensions::scaled.hsep_wide, !rtl);
 

	
 
	int normal_text_y_offset = (ir.Height() - GetCharacterHeight(FS_NORMAL)) / 2;
 
	int small_text_y_offset  = ir.Height() - GetCharacterHeight(FS_SMALL);
 
	int replace_icon_y_offset = (ir.Height() - replace_icon.height) / 2;
 

	
 
	int y = ir.top;
 
	for (; min < max; min++, y += step_size) {
 
		const auto &item = eng_list[min];
 
	for (auto it = first; it != last; ++it) {
 
		const auto &item = *it;
 
		uint indent       = item.indent * WidgetDimensions::scaled.hsep_indent;
 
		bool has_variants = (item.flags & EngineDisplayFlags::HasVariants) != EngineDisplayFlags::None;
 
		bool is_folded    = (item.flags & EngineDisplayFlags::IsFolded)    != EngineDisplayFlags::None;
 
		bool shaded       = (item.flags & EngineDisplayFlags::Shaded)      != EngineDisplayFlags::None;
 
		/* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */
 
		const uint num_engines = GetGroupNumEngines(_local_company, selected_group, item.engine_id);
 

	
 
		const Engine *e = Engine::Get(item.engine_id);
 
		bool hidden = HasBit(e->company_hidden, _local_company);
 
		StringID str = hidden ? STR_HIDDEN_ENGINE_NAME : STR_ENGINE_NAME;
 
		TextColour tc = (item.engine_id == selected_id) ? TC_WHITE : ((hidden | shaded) ? (TC_GREY | TC_FORCED | TC_NO_SHADE) : TC_BLACK);
 

	
 
		if (show_count) {
 
			/* relies on show_count to find 'Vehicle in use' panel of autoreplace window */
 
			SetDParam(0, PackEngineNameDParam(item.engine_id, EngineNameContext::AutoreplaceVehicleInUse, item.indent));
 
		} else {
 
			SetDParam(0, PackEngineNameDParam(item.engine_id, EngineNameContext::PurchaseList, item.indent));
 
		}
 
		Rect itr = tr.Indent(indent, rtl);
 
		DrawString(itr.left, itr.right, y + normal_text_y_offset, str, tc);
 
		int sprite_x = ir.Indent(indent + circle_width + WidgetDimensions::scaled.hsep_normal, rtl).WithWidth(sprite_width, rtl).left + sprite_left;
 
		DrawVehicleEngine(r.left, r.right, sprite_x, y + sprite_y_offset, item.engine_id, (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(item.engine_id, _local_company), EIT_PURCHASE);
 
		if (show_count) {
 
			SetDParam(0, num_engines);
 
			DrawString(cr.left, cr.right, y + small_text_y_offset, STR_JUST_COMMA, TC_BLACK, SA_RIGHT | SA_FORCE, false, FS_SMALL);
 
			if (EngineHasReplacementForCompany(Company::Get(_local_company), item.engine_id, selected_group)) DrawSprite(SPR_GROUP_REPLACE_ACTIVE, num_engines == 0 ? PALETTE_CRASH : PAL_NONE, rr.left, y + replace_icon_y_offset);
 
		}
 
		if (has_variants) {
 
			Rect fr = ir.Indent(indent, rtl).WithWidth(circle_width, rtl);
 
			DrawSpriteIgnorePadding(is_folded ? SPR_CIRCLE_FOLDED : SPR_CIRCLE_UNFOLDED, PAL_NONE, {fr.left, y, fr.right, y + ir.Height() - 1}, SA_CENTER);
 
		}
 
		if (indent > 0) {
 
			/* Draw tree lines */
 
			Rect fr = ir.Indent(indent - WidgetDimensions::scaled.hsep_indent, rtl).WithWidth(circle_width, rtl);
 
			int ycenter = y + normal_text_y_offset + GetCharacterHeight(FS_NORMAL) / 2;
 
			bool continues = (min + 1U) < eng_list.size() && eng_list[min + 1].indent == item.indent;
 
			bool continues = std::next(it) != std::end(eng_list) && std::next(it)->indent == item.indent;
 
			GfxDrawLine(fr.left + circle_width / 2, y - WidgetDimensions::scaled.matrix.top, fr.left + circle_width / 2, continues ? y - WidgetDimensions::scaled.matrix.top + step_size - 1 : ycenter, linecolour, WidgetDimensions::scaled.fullbevel.top);
 
			GfxDrawLine(fr.left + circle_width / 2, ycenter, fr.right, ycenter, linecolour, WidgetDimensions::scaled.fullbevel.top);
 
		}
 
		y += step_size;
 
	}
 
}
 

	
 
/**
 
 * Display the dropdown for the vehicle sort criteria.
 
 * @param w Parent window (holds the dropdown button).
 
 * @param vehicle_type %Vehicle type being sorted.
 
 * @param selected Currently selected sort criterium.
 
 * @param button Widget button.
 
 */
 
void DisplayVehicleSortDropDown(Window *w, VehicleType vehicle_type, int selected, WidgetID button)
 
{
 
	uint32_t hidden_mask = 0;
 
	/* Disable sorting by power or tractive effort when the original acceleration model for road vehicles is being used. */
 
	if (vehicle_type == VEH_ROAD && _settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL) {
 
		SetBit(hidden_mask, 3); // power
 
		SetBit(hidden_mask, 4); // tractive effort
 
		SetBit(hidden_mask, 8); // power by running costs
 
	}
 
	/* Disable sorting by tractive effort when the original acceleration model for trains is being used. */
 
	if (vehicle_type == VEH_TRAIN && _settings_game.vehicle.train_acceleration_model == AM_ORIGINAL) {
 
		SetBit(hidden_mask, 4); // tractive effort
 
	}
 
	ShowDropDownMenu(w, _engine_sort_listing[vehicle_type], selected, button, 0, hidden_mask);
 
@@ -1770,50 +1768,49 @@ struct BuildVehicleWindow : Window {
 
			case WID_BV_BUILD:
 
				*size = GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON + this->vehicle_type);
 
				*size = maxdim(*size, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_BUTTON + this->vehicle_type));
 
				size->width += padding.width;
 
				size->height += padding.height;
 
				break;
 

	
 
			case WID_BV_SHOW_HIDE:
 
				*size = GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type);
 
				*size = maxdim(*size, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type));
 
				size->width += padding.width;
 
				size->height += padding.height;
 
				break;
 
		}
 
	}
 

	
 
	void DrawWidget(const Rect &r, WidgetID widget) const override
 
	{
 
		switch (widget) {
 
			case WID_BV_LIST:
 
				DrawEngineList(
 
					this->vehicle_type,
 
					r,
 
					this->eng_list,
 
					this->vscroll->GetPosition(),
 
					static_cast<uint16_t>(std::min<size_t>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->eng_list.size())),
 
					*this->vscroll,
 
					this->sel_engine,
 
					false,
 
					DEFAULT_GROUP
 
				);
 
				break;
 

	
 
			case WID_BV_SORT_ASCENDING_DESCENDING:
 
				this->DrawSortButtonState(WID_BV_SORT_ASCENDING_DESCENDING, this->descending_sort_order ? SBS_DOWN : SBS_UP);
 
				break;
 
		}
 
	}
 

	
 
	void OnPaint() override
 
	{
 
		this->GenerateBuildList();
 
		this->vscroll->SetCount(this->eng_list.size());
 

	
 
		this->SetWidgetsDisabledState(this->sel_engine == INVALID_ENGINE, WID_BV_SHOW_HIDE, WID_BV_BUILD);
 

	
 
		/* Disable renaming engines in network games if you are not the server. */
 
		this->SetWidgetDisabledState(WID_BV_RENAME, this->sel_engine == INVALID_ENGINE || (_networking && !_network_server));
 

	
 
		this->DrawWidgets();
 

	
0 comments (0 inline, 0 general)