mirror of https://github.com/OpenTTD/OpenTTD.git
Feature: Contextual actions for vehicles grouped by shared orders (#8425)
This commit is contained in:
parent
5e14a20b3b
commit
8a78fa7121
|
@ -24,8 +24,10 @@
|
||||||
#include "tilehighlight_func.h"
|
#include "tilehighlight_func.h"
|
||||||
#include "window_gui.h"
|
#include "window_gui.h"
|
||||||
#include "vehiclelist.h"
|
#include "vehiclelist.h"
|
||||||
|
#include "vehicle_func.h"
|
||||||
#include "order_backup.h"
|
#include "order_backup.h"
|
||||||
#include "zoom_func.h"
|
#include "zoom_func.h"
|
||||||
|
#include "error.h"
|
||||||
#include "depot_cmd.h"
|
#include "depot_cmd.h"
|
||||||
#include "train_cmd.h"
|
#include "train_cmd.h"
|
||||||
#include "vehicle_cmd.h"
|
#include "vehicle_cmd.h"
|
||||||
|
@ -917,6 +919,49 @@ struct DepotWindow : Window {
|
||||||
return true;
|
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
|
void OnPlaceObjectAbort() override
|
||||||
{
|
{
|
||||||
/* abort clone */
|
/* abort clone */
|
||||||
|
|
|
@ -884,14 +884,14 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
case GB_SHARED_ORDERS: {
|
case GB_SHARED_ORDERS: {
|
||||||
const Vehicle *v = vehgroup.vehicles_begin[0];
|
if (!VehicleClicked(vehgroup)) {
|
||||||
/* We do not support VehicleClicked() here since the contextual action may only make sense for individual vehicles */
|
const Vehicle* v = vehgroup.vehicles_begin[0];
|
||||||
|
if (vindex == v->index) {
|
||||||
if (vindex == v->index) {
|
if (vehgroup.NumVehicles() == 1) {
|
||||||
if (vehgroup.NumVehicles() == 1) {
|
ShowVehicleViewWindow(v);
|
||||||
ShowVehicleViewWindow(v);
|
} else {
|
||||||
} else {
|
ShowVehicleListWindow(v);
|
||||||
ShowVehicleListWindow(v);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -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_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 :{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_AT_ALL_EXPLANATION :{WHITE}Change your NewGRF configuration
|
||||||
STR_ERROR_NO_VEHICLES_AVAILABLE_YET :{WHITE}No vehicles are available yet
|
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_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 :{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_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_SHARE_ORDER_LIST :{WHITE}Can't share order list...
|
||||||
STR_ERROR_CAN_T_STOP_SHARING_ORDER_LIST :{WHITE}Can't stop sharing order list...
|
STR_ERROR_CAN_T_STOP_SHARING_ORDER_LIST :{WHITE}Can't stop sharing order list...
|
||||||
|
|
|
@ -29,6 +29,9 @@
|
||||||
#include "aircraft.h"
|
#include "aircraft.h"
|
||||||
#include "engine_func.h"
|
#include "engine_func.h"
|
||||||
#include "vehicle_func.h"
|
#include "vehicle_func.h"
|
||||||
|
#include "vehiclelist.h"
|
||||||
|
#include "vehicle_func.h"
|
||||||
|
#include "error.h"
|
||||||
#include "order_cmd.h"
|
#include "order_cmd.h"
|
||||||
#include "company_cmd.h"
|
#include "company_cmd.h"
|
||||||
|
|
||||||
|
@ -1466,6 +1469,40 @@ public:
|
||||||
return true;
|
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
|
void OnPlaceObjectAbort() override
|
||||||
{
|
{
|
||||||
this->goto_type = OPOS_NONE;
|
this->goto_type = OPOS_NONE;
|
||||||
|
|
|
@ -3013,3 +3013,39 @@ uint32 Vehicle::GetDisplayMinPowerToWeight() const
|
||||||
if (max_weight == 0) return 0;
|
if (max_weight == 0) return 0;
|
||||||
return GetGroundVehicleCache()->cached_power * 10u / max_weight;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -174,4 +174,7 @@ void GetVehicleSet(VehicleSet &set, Vehicle *v, uint8 num_vehicles);
|
||||||
|
|
||||||
void CheckCargoCapacity(Vehicle *v);
|
void CheckCargoCapacity(Vehicle *v);
|
||||||
|
|
||||||
|
bool VehiclesHaveSameEngineList(const Vehicle *v1, const Vehicle *v2);
|
||||||
|
bool VehiclesHaveSameOrderList(const Vehicle *v1, const Vehicle *v2);
|
||||||
|
|
||||||
#endif /* VEHICLE_FUNC_H */
|
#endif /* VEHICLE_FUNC_H */
|
||||||
|
|
|
@ -2018,16 +2018,16 @@ public:
|
||||||
|
|
||||||
case GB_SHARED_ORDERS: {
|
case GB_SHARED_ORDERS: {
|
||||||
assert(vehgroup.NumVehicles() > 0);
|
assert(vehgroup.NumVehicles() > 0);
|
||||||
const Vehicle *v = vehgroup.vehicles_begin[0];
|
if (!VehicleClicked(vehgroup)) {
|
||||||
/* We do not support VehicleClicked() here since the contextual action may only make sense for individual vehicles */
|
const Vehicle *v = vehgroup.vehicles_begin[0];
|
||||||
|
if (_ctrl_pressed) {
|
||||||
if (_ctrl_pressed) {
|
ShowOrdersWindow(v);
|
||||||
ShowOrdersWindow(v);
|
|
||||||
} else {
|
|
||||||
if (vehgroup.NumVehicles() == 1) {
|
|
||||||
ShowVehicleViewWindow(v);
|
|
||||||
} else {
|
} else {
|
||||||
ShowVehicleListWindow(v);
|
if (vehgroup.NumVehicles() == 1) {
|
||||||
|
ShowVehicleViewWindow(v);
|
||||||
|
} else {
|
||||||
|
ShowVehicleListWindow(v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -3288,6 +3288,33 @@ bool VehicleClicked(const Vehicle *v)
|
||||||
return _thd.GetCallbackWnd()->OnVehicleSelect(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)
|
void StopGlobalFollowVehicle(const Vehicle *v)
|
||||||
{
|
{
|
||||||
Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
|
Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
#include "window_type.h"
|
#include "window_type.h"
|
||||||
#include "vehicle_type.h"
|
#include "vehicle_type.h"
|
||||||
|
#include "vehicle_gui_base.h"
|
||||||
|
#include "vehiclelist.h"
|
||||||
#include "order_type.h"
|
#include "order_type.h"
|
||||||
#include "station_type.h"
|
#include "station_type.h"
|
||||||
#include "engine_type.h"
|
#include "engine_type.h"
|
||||||
|
@ -102,6 +104,8 @@ static inline WindowClass GetWindowClassForVehicleType(VehicleType vt)
|
||||||
/* Unified window procedure */
|
/* Unified window procedure */
|
||||||
void ShowVehicleViewWindow(const Vehicle *v);
|
void ShowVehicleViewWindow(const Vehicle *v);
|
||||||
bool VehicleClicked(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);
|
void StartStopVehicle(const Vehicle *v, bool texteffect);
|
||||||
|
|
||||||
Vehicle *CheckClickOnVehicle(const struct Viewport *vp, int x, int y);
|
Vehicle *CheckClickOnVehicle(const struct Viewport *vp, int x, int y);
|
||||||
|
|
|
@ -11,7 +11,10 @@
|
||||||
#define WINDOW_GUI_H
|
#define WINDOW_GUI_H
|
||||||
|
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "vehiclelist.h"
|
||||||
#include "vehicle_type.h"
|
#include "vehicle_type.h"
|
||||||
#include "viewport_type.h"
|
#include "viewport_type.h"
|
||||||
#include "company_type.h"
|
#include "company_type.h"
|
||||||
|
@ -681,11 +684,20 @@ public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user clicked on a vehicle while HT_VEHICLE has been set.
|
* The user clicked on a vehicle while HT_VEHICLE has been set.
|
||||||
* @param v clicked vehicle. It is guaranteed to be v->IsPrimaryVehicle() == true
|
* @param v clicked vehicle
|
||||||
* @return True if the click is handled, false if it is ignored.
|
* @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; }
|
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.
|
* 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.
|
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.
|
* Get the nested widget with number \a widnum from the nested widget tree.
|
||||||
* @tparam NWID Type of the nested widget.
|
* @tparam NWID Type of the nested widget.
|
||||||
|
|
Loading…
Reference in New Issue