Feature: Contextual actions for vehicles grouped by shared orders (#8425)

This commit is contained in:
Bernard Teo 2022-11-27 01:03:21 +08:00 committed by GitHub
parent 5e14a20b3b
commit 8a78fa7121
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 200 additions and 19 deletions

View File

@ -24,8 +24,10 @@
#include "tilehighlight_func.h"
#include "window_gui.h"
#include "vehiclelist.h"
#include "vehicle_func.h"
#include "order_backup.h"
#include "zoom_func.h"
#include "error.h"
#include "depot_cmd.h"
#include "train_cmd.h"
#include "vehicle_cmd.h"
@ -917,6 +919,49 @@ struct DepotWindow : Window {
return true;
}
/**
* Clones a vehicle from a vehicle list. If this doesn't make sense (because not all vehicles in the list have the same orders), then it displays an error.
* @return This always returns true, which indicates that the contextual action handled the mouse click.
* Note that it's correct behaviour to always handle the click even though an error is displayed,
* because users aren't going to expect the default action to be performed just because they overlooked that cloning doesn't make sense.
*/
bool OnVehicleSelect(VehicleList::const_iterator begin, VehicleList::const_iterator end) override
{
if (!_ctrl_pressed) {
/* If CTRL is not pressed: If all the vehicles in this list have the same orders, then copy orders */
if (AllEqual(begin, end, [](const Vehicle *v1, const Vehicle *v2) {
return VehiclesHaveSameEngineList(v1, v2);
})) {
if (AllEqual(begin, end, [](const Vehicle *v1, const Vehicle *v2) {
return VehiclesHaveSameOrderList(v1, v2);
})) {
OnVehicleSelect(*begin);
} else {
ShowErrorMessage(STR_ERROR_CAN_T_BUY_TRAIN + (*begin)->type, STR_ERROR_CAN_T_COPY_ORDER_VEHICLE_LIST, WL_INFO);
}
} else {
ShowErrorMessage(STR_ERROR_CAN_T_BUY_TRAIN + (*begin)->type, STR_ERROR_CAN_T_CLONE_VEHICLE_LIST, WL_INFO);
}
} else {
/* If CTRL is pressed: If all the vehicles in this list share orders, then copy orders */
if (AllEqual(begin, end, [](const Vehicle *v1, const Vehicle *v2) {
return VehiclesHaveSameEngineList(v1, v2);
})) {
if (AllEqual(begin, end, [](const Vehicle *v1, const Vehicle *v2) {
return v1->FirstShared() == v2->FirstShared();
})) {
OnVehicleSelect(*begin);
} else {
ShowErrorMessage(STR_ERROR_CAN_T_BUY_TRAIN + (*begin)->type, STR_ERROR_CAN_T_SHARE_ORDER_VEHICLE_LIST, WL_INFO);
}
} else {
ShowErrorMessage(STR_ERROR_CAN_T_BUY_TRAIN + (*begin)->type, STR_ERROR_CAN_T_CLONE_VEHICLE_LIST, WL_INFO);
}
}
return true;
}
void OnPlaceObjectAbort() override
{
/* abort clone */

View File

@ -884,14 +884,14 @@ public:
}
case GB_SHARED_ORDERS: {
const Vehicle *v = vehgroup.vehicles_begin[0];
/* We do not support VehicleClicked() here since the contextual action may only make sense for individual vehicles */
if (vindex == v->index) {
if (vehgroup.NumVehicles() == 1) {
ShowVehicleViewWindow(v);
} else {
ShowVehicleListWindow(v);
if (!VehicleClicked(vehgroup)) {
const Vehicle* v = vehgroup.vehicles_begin[0];
if (vindex == v->index) {
if (vehgroup.NumVehicles() == 1) {
ShowVehicleViewWindow(v);
} else {
ShowVehicleListWindow(v);
}
}
}
break;

View File

@ -5029,6 +5029,8 @@ STR_ERROR_CAN_T_CHANGE_SERVICING :{WHITE}Can't ch
STR_ERROR_VEHICLE_IS_DESTROYED :{WHITE}... vehicle is destroyed
STR_ERROR_CAN_T_CLONE_VEHICLE_LIST :{WHITE}... not all vehicles are identical
STR_ERROR_NO_VEHICLES_AVAILABLE_AT_ALL :{WHITE}No vehicles will be available at all
STR_ERROR_NO_VEHICLES_AVAILABLE_AT_ALL_EXPLANATION :{WHITE}Change your NewGRF configuration
STR_ERROR_NO_VEHICLES_AVAILABLE_YET :{WHITE}No vehicles are available yet
@ -5055,6 +5057,8 @@ STR_ERROR_CAN_T_SKIP_TO_ORDER :{WHITE}Can't sk
STR_ERROR_CAN_T_COPY_SHARE_ORDER :{WHITE}... vehicle can't go to all stations
STR_ERROR_CAN_T_ADD_ORDER :{WHITE}... vehicle can't go to that station
STR_ERROR_CAN_T_ADD_ORDER_SHARED :{WHITE}... a vehicle sharing this order can't go to that station
STR_ERROR_CAN_T_COPY_ORDER_VEHICLE_LIST :{WHITE}... not all vehicles have the same orders
STR_ERROR_CAN_T_SHARE_ORDER_VEHICLE_LIST :{WHITE}... not all vehicles are sharing orders
STR_ERROR_CAN_T_SHARE_ORDER_LIST :{WHITE}Can't share order list...
STR_ERROR_CAN_T_STOP_SHARING_ORDER_LIST :{WHITE}Can't stop sharing order list...

View File

@ -29,6 +29,9 @@
#include "aircraft.h"
#include "engine_func.h"
#include "vehicle_func.h"
#include "vehiclelist.h"
#include "vehicle_func.h"
#include "error.h"
#include "order_cmd.h"
#include "company_cmd.h"
@ -1466,6 +1469,40 @@ public:
return true;
}
/**
* Clones an order list from a vehicle list. If this doesn't make sense (because not all vehicles in the list have the same orders), then it displays an error.
* @return This always returns true, which indicates that the contextual action handled the mouse click.
* Note that it's correct behaviour to always handle the click even though an error is displayed,
* because users aren't going to expect the default action to be performed just because they overlooked that cloning doesn't make sense.
*/
bool OnVehicleSelect(VehicleList::const_iterator begin, VehicleList::const_iterator end) override
{
bool share_order = _ctrl_pressed || this->goto_type == OPOS_SHARE;
if (this->vehicle->GetNumOrders() != 0 && !share_order) return false;
if (!share_order) {
/* If CTRL is not pressed: If all the vehicles in this list have the same orders, then copy orders */
if (AllEqual(begin, end, [](const Vehicle *v1, const Vehicle *v2) {
return VehiclesHaveSameOrderList(v1, v2);
})) {
OnVehicleSelect(*begin);
} else {
ShowErrorMessage(STR_ERROR_CAN_T_COPY_ORDER_LIST, STR_ERROR_CAN_T_COPY_ORDER_VEHICLE_LIST, WL_INFO);
}
} else {
/* If CTRL is pressed: If all the vehicles in this list share orders, then copy orders */
if (AllEqual(begin, end, [](const Vehicle *v1, const Vehicle *v2) {
return v1->FirstShared() == v2->FirstShared();
})) {
OnVehicleSelect(*begin);
} else {
ShowErrorMessage(STR_ERROR_CAN_T_SHARE_ORDER_LIST, STR_ERROR_CAN_T_SHARE_ORDER_VEHICLE_LIST, WL_INFO);
}
}
return true;
}
void OnPlaceObjectAbort() override
{
this->goto_type = OPOS_NONE;

View File

@ -3013,3 +3013,39 @@ uint32 Vehicle::GetDisplayMinPowerToWeight() const
if (max_weight == 0) return 0;
return GetGroundVehicleCache()->cached_power * 10u / max_weight;
}
/**
* Checks if two vehicle chains have the same list of engines.
* @param v1 First vehicle chain.
* @param v1 Second vehicle chain.
* @return True if same, false if different.
*/
bool VehiclesHaveSameEngineList(const Vehicle *v1, const Vehicle *v2)
{
while (true) {
if (v1 == nullptr && v2 == nullptr) return true;
if (v1 == nullptr || v2 == nullptr) return false;
if (v1->GetEngine() != v2->GetEngine()) return false;
v1 = v1->GetNextVehicle();
v2 = v2->GetNextVehicle();
}
}
/**
* Checks if two vehicles have the same list of orders.
* @param v1 First vehicles.
* @param v1 Second vehicles.
* @return True if same, false if different.
*/
bool VehiclesHaveSameOrderList(const Vehicle *v1, const Vehicle *v2)
{
const Order *o1 = v1->GetFirstOrder();
const Order *o2 = v2->GetFirstOrder();
while (true) {
if (o1 == nullptr && o2 == nullptr) return true;
if (o1 == nullptr || o2 == nullptr) return false;
if (!o1->Equals(*o2)) return false;
o1 = o1->next;
o2 = o2->next;
}
}

View File

@ -174,4 +174,7 @@ void GetVehicleSet(VehicleSet &set, Vehicle *v, uint8 num_vehicles);
void CheckCargoCapacity(Vehicle *v);
bool VehiclesHaveSameEngineList(const Vehicle *v1, const Vehicle *v2);
bool VehiclesHaveSameOrderList(const Vehicle *v1, const Vehicle *v2);
#endif /* VEHICLE_FUNC_H */

View File

@ -2018,16 +2018,16 @@ public:
case GB_SHARED_ORDERS: {
assert(vehgroup.NumVehicles() > 0);
const Vehicle *v = vehgroup.vehicles_begin[0];
/* We do not support VehicleClicked() here since the contextual action may only make sense for individual vehicles */
if (_ctrl_pressed) {
ShowOrdersWindow(v);
} else {
if (vehgroup.NumVehicles() == 1) {
ShowVehicleViewWindow(v);
if (!VehicleClicked(vehgroup)) {
const Vehicle *v = vehgroup.vehicles_begin[0];
if (_ctrl_pressed) {
ShowOrdersWindow(v);
} else {
ShowVehicleListWindow(v);
if (vehgroup.NumVehicles() == 1) {
ShowVehicleViewWindow(v);
} else {
ShowVehicleListWindow(v);
}
}
}
break;
@ -3288,6 +3288,33 @@ bool VehicleClicked(const Vehicle *v)
return _thd.GetCallbackWnd()->OnVehicleSelect(v);
}
/**
* Dispatch a "vehicle group selected" event if any window waits for it.
* @param begin iterator to the start of the range of vehicles
* @param end iterator to the end of the range of vehicles
* @return did any window accept vehicle group selection?
*/
bool VehicleClicked(VehicleList::const_iterator begin, VehicleList::const_iterator end)
{
assert(begin != end);
if (!(_thd.place_mode & HT_VEHICLE)) return false;
/* If there is only one vehicle in the group, act as if we clicked a single vehicle */
if (begin + 1 == end) return _thd.GetCallbackWnd()->OnVehicleSelect(*begin);
return _thd.GetCallbackWnd()->OnVehicleSelect(begin, end);
}
/**
* Dispatch a "vehicle group selected" event if any window waits for it.
* @param vehgroup the GUIVehicleGroup representing the vehicle group
* @return did any window accept vehicle group selection?
*/
bool VehicleClicked(const GUIVehicleGroup &vehgroup)
{
return VehicleClicked(vehgroup.vehicles_begin, vehgroup.vehicles_end);
}
void StopGlobalFollowVehicle(const Vehicle *v)
{
Window *w = FindWindowById(WC_MAIN_WINDOW, 0);

View File

@ -12,6 +12,8 @@
#include "window_type.h"
#include "vehicle_type.h"
#include "vehicle_gui_base.h"
#include "vehiclelist.h"
#include "order_type.h"
#include "station_type.h"
#include "engine_type.h"
@ -102,6 +104,8 @@ static inline WindowClass GetWindowClassForVehicleType(VehicleType vt)
/* Unified window procedure */
void ShowVehicleViewWindow(const Vehicle *v);
bool VehicleClicked(const Vehicle *v);
bool VehicleClicked(VehicleList::const_iterator begin, VehicleList::const_iterator end);
bool VehicleClicked(const GUIVehicleGroup &vehgroup);
void StartStopVehicle(const Vehicle *v, bool texteffect);
Vehicle *CheckClickOnVehicle(const struct Viewport *vp, int x, int y);

View File

@ -11,7 +11,10 @@
#define WINDOW_GUI_H
#include <list>
#include <algorithm>
#include <functional>
#include "vehiclelist.h"
#include "vehicle_type.h"
#include "viewport_type.h"
#include "company_type.h"
@ -681,11 +684,20 @@ public:
/**
* The user clicked on a vehicle while HT_VEHICLE has been set.
* @param v clicked vehicle. It is guaranteed to be v->IsPrimaryVehicle() == true
* @return True if the click is handled, false if it is ignored.
* @param v clicked vehicle
* @return true if the click is handled, false if it is ignored
* @pre v->IsPrimaryVehicle() == true
*/
virtual bool OnVehicleSelect(const struct Vehicle *v) { return false; }
/**
* The user clicked on a vehicle while HT_VEHICLE has been set.
* @param v clicked vehicle
* @return True if the click is handled, false if it is ignored
* @pre v->IsPrimaryVehicle() == true
*/
virtual bool OnVehicleSelect(VehicleList::const_iterator begin, VehicleList::const_iterator end) { return false; }
/**
* The user cancelled a tile highlight mode that has been set.
*/
@ -806,6 +818,19 @@ public:
using IterateFromFront = AllWindows<true>; //!< Iterate all windows in Z order from front to back.
};
/**
* Generic helper function that checks if all elements of the range are equal with respect to the given predicate.
* @param begin The start of the range.
* @param end The end of the range.
* @param pred The predicate to use.
* @return True if all elements are equal, false otherwise.
*/
template <class It, class Pred>
inline bool AllEqual(It begin, It end, Pred pred)
{
return std::adjacent_find(begin, end, std::not_fn(pred)) == end;
}
/**
* Get the nested widget with number \a widnum from the nested widget tree.
* @tparam NWID Type of the nested widget.