4 #include "direction_type.h"
6 #include "strings_func.h"
7 #include "window_func.h"
8 #include "company_func.h"
9 #include "window_gui.h"
10 #include "settings_func.h"
11 #include "core/geometry_func.hpp"
12 #include "table/sprites.h"
13 #include "table/strings.h"
14 #include "viewport_func.h"
15 #include "window_func.h"
17 #include "textbuf_gui.h"
18 #include "command_func.h"
19 #include "depot_base.h"
20 #include "vehicle_gui.h"
21 #include "spritecache.h"
22 #include "strings_func.h"
23 #include "window_func.h"
24 #include "vehicle_func.h"
25 #include "company_func.h"
26 #include "tilehighlight_func.h"
27 #include "window_gui.h"
28 #include "vehiclelist.h"
29 #include "order_backup.h"
31 #include "company_base.h"
33 #include "tbtr_template_gui_create.h"
34 #include "tbtr_template_vehicle.h"
35 #include "tbtr_template_vehicle_func.h"
39 class TemplateReplaceWindow
;
41 // some space in front of the virtual train in the matrix
42 uint16 TRAIN_FRONT_SPACE
= 16;
44 enum TemplateReplaceWindowWidgets
{
48 TCW_SCROLLBAR_H_NEW_TMPL
,
49 TCW_SCROLLBAR_V_NEW_TMPL
,
58 static const NWidgetPart _widgets
[] = {
59 NWidget(NWID_HORIZONTAL
),
60 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
61 NWidget(WWT_CAPTION
, COLOUR_GREY
, TCW_CAPTION
), SetDataTip(STR_TMPL_CREATEGUI_TITLE
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
62 NWidget(WWT_SHADEBOX
, COLOUR_GREY
),
63 NWidget(WWT_DEFSIZEBOX
, COLOUR_GREY
),
64 NWidget(WWT_STICKYBOX
, COLOUR_GREY
),
66 NWidget(NWID_HORIZONTAL
),
67 NWidget(NWID_VERTICAL
),
68 NWidget(WWT_PANEL
, COLOUR_GREY
, TCW_NEW_TMPL_PANEL
), SetMinimalSize(250, 30), SetResize(1, 0), SetScrollbar(TCW_SCROLLBAR_H_NEW_TMPL
), EndContainer(),
69 NWidget(WWT_PANEL
, COLOUR_GREY
, TCW_INFO_PANEL
), SetMinimalSize(250, 100), SetResize(1, 1), SetScrollbar(TCW_SCROLLBAR_V_NEW_TMPL
), EndContainer(),
70 NWidget(NWID_HSCROLLBAR
, COLOUR_GREY
, TCW_SCROLLBAR_H_NEW_TMPL
),
72 NWidget(WWT_IMGBTN
, COLOUR_GREY
, TCW_SELL_TMPL
), SetMinimalSize(40, 40), SetDataTip(0x0, STR_NULL
), SetResize(0, 1), SetFill(0, 1),
73 NWidget(NWID_VSCROLLBAR
, COLOUR_GREY
, TCW_SCROLLBAR_V_NEW_TMPL
),
75 NWidget(NWID_HORIZONTAL
),
76 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, TCW_OK
), SetMinimalSize(52, 12), SetResize(1, 0), SetDataTip(STR_TMPL_CONFIRM
, STR_TMPL_CONFIRM
),
77 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, TCW_NEW
), SetMinimalSize(52, 12), SetResize(1, 0), SetDataTip(STR_TMPL_NEW
, STR_TMPL_NEW
),
78 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, TCW_CLONE
), SetMinimalSize(52, 12), SetResize(1, 0), SetDataTip(STR_TMPL_CREATE_CLONE_VEH
, STR_TMPL_CREATE_CLONE_VEH
),
79 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, TCW_REFIT
), SetMinimalSize(52, 12), SetResize(1, 0), SetDataTip(STR_TMPL_REFIT
, STR_TMPL_REFIT
),
80 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, TCW_CANCEL
), SetMinimalSize(52, 12), SetResize(1, 0), SetDataTip(STR_TMPL_CANCEL
, STR_TMPL_CANCEL
),
81 NWidget(WWT_RESIZEBOX
, COLOUR_GREY
),
85 static WindowDesc
_template_create_window_desc(
86 WDP_AUTO
, // window position
87 "template create window", // const char* ini_key
88 456, 100, // window size
89 WC_CREATE_TEMPLATE
, // window class
90 WC_NONE
, // parent window class
91 WDF_CONSTRUCTION
, // window flags
92 _widgets
, lengthof(_widgets
) // widgets + num widgets
95 void ShowTemplateTrainBuildVehicleWindow(Train
**virtual_train
);
97 static void TrainDepotMoveVehicle(const Vehicle
*wagon
, VehicleID sel
, const Vehicle
*head
)
99 const Vehicle
*v
= Vehicle::Get(sel
);
101 if (v
== wagon
) return;
103 if (wagon
== nullptr) {
104 if (head
!= nullptr) wagon
= head
->Last();
106 wagon
= wagon
->Previous();
107 if (wagon
== nullptr) return;
110 if (wagon
== v
) return;
112 DoCommandP(v
->tile
, v
->index
| (_ctrl_pressed
? 1 : 0) << 20 | 1 << 21, wagon
== nullptr ? INVALID_VEHICLE
: wagon
->index
, CMD_MOVE_RAIL_VEHICLE
| CMD_MSG(STR_ERROR_CAN_T_MOVE_VEHICLE
), CcVirtualTrainWagonsMoved
);
115 class TemplateCreateWindow
: public Window
{
120 Train
* virtual_train
;
121 bool *create_window_open
; /// used to notify main window of progress (dummy way of disabling 'delete' while editing a template)
123 VehicleID vehicle_over
;
124 bool sell_hovered
; ///< A vehicle is being dragged/hovered over the sell button.
125 uint32 template_index
;
128 TemplateCreateWindow(WindowDesc
* _wdesc
, TemplateVehicle
*to_edit
, bool *window_open
, int step_h
) : Window(_wdesc
)
130 this->line_height
= step_h
;
131 this->CreateNestedTree(_wdesc
!= nullptr);
132 this->hscroll
= this->GetScrollbar(TCW_SCROLLBAR_H_NEW_TMPL
);
133 this->vscroll
= this->GetScrollbar(TCW_SCROLLBAR_V_NEW_TMPL
);
134 this->FinishInitNested(VEH_TRAIN
);
136 this->GetWidget
<NWidgetCore
>(TCW_SELL_TMPL
)->widget_data
= SPR_SELL_TRAIN
;
138 this->owner
= _local_company
;
140 this->create_window_open
= window_open
;
141 this->template_index
= (to_edit
!= nullptr) ? to_edit
->index
: INVALID_VEHICLE
;
143 this->sel
= INVALID_VEHICLE
;
144 this->vehicle_over
= INVALID_VEHICLE
;
145 this->sell_hovered
= false;
147 if (to_edit
!= nullptr) {
148 DoCommandP(0, to_edit
->index
, 0, CMD_VIRTUAL_TRAIN_FROM_TEMPLATE_VEHICLE
| CMD_MSG(STR_TMPL_CANT_CREATE
), CcSetVirtualTrain
);
151 this->resize
.step_height
= 1;
156 ~TemplateCreateWindow()
158 if (virtual_train
!= nullptr) {
159 DoCommandP(0, virtual_train
->index
, 0, CMD_DELETE_VIRTUAL_TRAIN
);
160 virtual_train
= nullptr;
163 SetWindowClassesDirty(WC_TRAINS_LIST
);
164 SetWindowClassesDirty(WC_TRACE_RESTRICT_SLOTS
);
167 *create_window_open
= false;
168 DeleteWindowById(WC_BUILD_VIRTUAL_TRAIN
, this->window_number
);
169 InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN
);
172 void SetVirtualTrain(Train
* const train
)
174 if (virtual_train
!= nullptr) {
175 DoCommandP(0, virtual_train
->index
, 0, CMD_DELETE_VIRTUAL_TRAIN
);
178 virtual_train
= train
;
179 if (virtual_train
!= nullptr) {
180 assert(HasBit(virtual_train
->subtype
, GVSF_VIRTUAL
));
185 virtual void OnResize()
187 NWidgetCore
*template_panel
= this->GetWidget
<NWidgetCore
>(TCW_NEW_TMPL_PANEL
);
188 this->hscroll
->SetCapacity(template_panel
->current_x
);
190 NWidgetCore
*info_panel
= this->GetWidget
<NWidgetCore
>(TCW_INFO_PANEL
);
191 this->vscroll
->SetCapacity(info_panel
->current_y
);
195 virtual void OnInvalidateData(int data
= 0, bool gui_scope
= true)
197 if(!gui_scope
) return;
199 if (this->template_index
!= INVALID_VEHICLE
) {
200 if (TemplateVehicle::GetIfValid(this->template_index
) == nullptr) {
209 virtual void OnClick(Point pt
, int widget
, int click_count
)
212 case TCW_NEW_TMPL_PANEL
: {
213 NWidgetBase
*nwi
= this->GetWidget
<NWidgetBase
>(TCW_NEW_TMPL_PANEL
);
214 ClickedOnVehiclePanel(pt
.x
- nwi
->pos_x
, pt
.y
- nwi
->pos_y
);
218 ShowTemplateTrainBuildVehicleWindow(&virtual_train
);
222 this->SetWidgetDirty(TCW_CLONE
);
223 this->ToggleWidgetLoweredState(TCW_CLONE
);
224 if (this->IsWidgetLowered(TCW_CLONE
)) {
225 static const CursorID clone_icon
= SPR_CURSOR_CLONE_TRAIN
;
226 SetObjectToPlaceWnd(clone_icon
, PAL_NONE
, HT_VEHICLE
, this);
228 ResetObjectToPlace();
233 if (virtual_train
!= nullptr) {
234 DoCommandP(0, this->template_index
, virtual_train
->index
, CMD_REPLACE_TEMPLATE_VEHICLE
);
235 virtual_train
= nullptr;
236 } else if (this->template_index
!= INVALID_VEHICLE
) {
237 DoCommandP(0, this->template_index
, 0, CMD_DELETE_TEMPLATE_VEHICLE
);
247 if (virtual_train
!= nullptr) {
248 ShowVehicleRefitWindow(virtual_train
, INVALID_VEH_ORDER_ID
, this, false, true);
255 virtual bool OnVehicleSelect(const Vehicle
*v
)
257 // throw away the current virtual train
258 if (virtual_train
!= nullptr) {
259 DoCommandP(0, virtual_train
->index
, 0, CMD_DELETE_VIRTUAL_TRAIN
);
260 virtual_train
= nullptr;
264 DoCommandP(0, v
->index
, 0, CMD_VIRTUAL_TRAIN_FROM_TRAIN
| CMD_MSG(STR_TMPL_CANT_CREATE
), CcSetVirtualTrain
);
265 this->ToggleWidgetLoweredState(TCW_CLONE
);
266 ResetObjectToPlace();
272 virtual void OnPlaceObjectAbort()
274 this->sel
= INVALID_VEHICLE
;
275 this->vehicle_over
= INVALID_VEHICLE
;
276 this->RaiseButtons();
280 virtual void DrawWidget(const Rect
&r
, int widget
) const
283 case TCW_NEW_TMPL_PANEL
: {
284 if ( this->virtual_train
) {
285 DrawTrainImage(virtual_train
, r
.left
+ TRAIN_FRONT_SPACE
, r
.right
- 25, r
.top
+ 2, this->sel
, EIT_IN_DEPOT
, this->hscroll
->GetPosition(), this->vehicle_over
);
286 SetDParam(0, CeilDiv(virtual_train
->gcache
.cached_total_length
* 10, TILE_SIZE
));
288 DrawString(r
.left
, r
.right
, r
.top
, STR_TINY_BLACK_DECIMAL
, TC_BLACK
, SA_RIGHT
);
292 case TCW_INFO_PANEL
: {
293 if ( this->virtual_train
) {
294 DrawPixelInfo tmp_dpi
, *old_dpi
;
296 if (!FillDrawPixelInfo(&tmp_dpi
, r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
)) break;
301 /* Draw vehicle performance info */
303 uint16 loaded_weight
= 0;
305 for (const Vehicle
*u
= this->virtual_train
; u
!= nullptr; u
= u
->Next()) {
306 loaded_weight
+= Train::From(u
)->GetLoadedWeight();
309 const GroundVehicleCache
*gcache
= this->virtual_train
->GetGroundVehicleCache();
310 double tractive_effort_loaded
= loaded_weight
* this->virtual_train
->GetRollingFriction();
311 double power
= gcache
->cached_power
* 746ll;
312 double max_te
= gcache
->cached_max_te
;
313 uint16 loaded_max_speed
= min(this->virtual_train
->GetDisplayMaxSpeed(), (tractive_effort_loaded
== 0 || tractive_effort_loaded
> max_te
) ? 0 : (3.6 * (power
/ tractive_effort_loaded
)));
315 SetDParam(0, loaded_weight
);
316 SetDParam(1, gcache
->cached_power
);
317 SetDParam(2, loaded_max_speed
);
318 SetDParam(3, gcache
->cached_max_te
/ 1000);
319 DrawString(8, r
.right
, 4 - this->vscroll
->GetPosition(), STR_VEHICLE_INFO_LOADED_WEIGHT_POWER_MAX_SPEED_MAX_TE
);
320 /* Draw cargo summary */
321 CargoArray cargo_caps
;
322 for ( const Train
*tmp
=this->virtual_train
; tmp
; tmp
=tmp
->Next() )
323 cargo_caps
[tmp
->cargo_type
] += tmp
->cargo_cap
;
324 int y
= 30 - this->vscroll
->GetPosition();
325 for (CargoID i
= 0; i
< NUM_CARGO
; ++i
) {
326 if ( cargo_caps
[i
] > 0 ) {
328 SetDParam(1, cargo_caps
[i
]);
329 SetDParam(2, _settings_game
.vehicle
.freight_trains
);
330 DrawString(8, r
.right
, y
, STR_TMPL_CARGO_SUMMARY
, TC_LIGHT_BLUE
, SA_LEFT
);
331 y
+= this->line_height
/3;
344 virtual void OnDragDrop(Point pt
, int widget
)
347 case TCW_NEW_TMPL_PANEL
: {
348 const Vehicle
*v
= nullptr;
349 VehicleID sel
= this->sel
;
351 this->sel
= INVALID_VEHICLE
;
354 NWidgetBase
*nwi
= this->GetWidget
<NWidgetBase
>(TCW_NEW_TMPL_PANEL
);
355 GetDepotVehiclePtData gdvp
= { nullptr, nullptr };
357 if (this->GetVehicleFromDepotWndPt(pt
.x
- nwi
->pos_x
, pt
.y
- nwi
->pos_y
, &v
, &gdvp
) == MODE_DRAG_VEHICLE
&& sel
!= INVALID_VEHICLE
) {
358 if (gdvp
.wagon
== nullptr || gdvp
.wagon
->index
!= sel
) {
359 this->vehicle_over
= INVALID_VEHICLE
;
360 TrainDepotMoveVehicle(gdvp
.wagon
, sel
, gdvp
.head
);
365 case TCW_SELL_TMPL
: {
366 if (this->IsWidgetDisabled(widget
)) return;
367 if (this->sel
== INVALID_VEHICLE
) return;
369 int sell_cmd
= (_ctrl_pressed
) ? 1 : 0;
371 Train
* train_to_delete
= Train::Get(this->sel
);
373 if (virtual_train
== train_to_delete
)
374 virtual_train
= (_ctrl_pressed
) ? nullptr : virtual_train
->GetNextUnit();
376 DoCommandP(0, this->sel
| sell_cmd
<< 20 | 1 << 21, 0, GetCmdSellVeh(VEH_TRAIN
), CcDeleteVirtualTrain
);
378 this->sel
= INVALID_VEHICLE
;
385 this->sel
= INVALID_VEHICLE
;
389 this->sell_hovered
= false;
390 _cursor
.vehchain
= false;
391 this->sel
= INVALID_VEHICLE
;
395 virtual void OnMouseDrag(Point pt
, int widget
)
397 if (this->sel
== INVALID_VEHICLE
) return;
399 bool is_sell_widget
= widget
== TCW_SELL_TMPL
;
400 if (is_sell_widget
!= this->sell_hovered
) {
401 this->sell_hovered
= is_sell_widget
;
402 this->SetWidgetLoweredState(TCW_SELL_TMPL
, is_sell_widget
);
403 this->SetWidgetDirty(TCW_SELL_TMPL
);
406 /* A rail vehicle is dragged.. */
407 if (widget
!= TCW_NEW_TMPL_PANEL
) { // ..outside of the depot matrix.
408 if (this->vehicle_over
!= INVALID_VEHICLE
) {
409 this->vehicle_over
= INVALID_VEHICLE
;
410 this->SetWidgetDirty(TCW_NEW_TMPL_PANEL
);
415 NWidgetBase
*matrix
= this->GetWidget
<NWidgetBase
>(widget
);
416 const Vehicle
*v
= nullptr;
417 GetDepotVehiclePtData gdvp
= {nullptr, nullptr};
419 if (this->GetVehicleFromDepotWndPt(pt
.x
- matrix
->pos_x
, pt
.y
- matrix
->pos_y
, &v
, &gdvp
) != MODE_DRAG_VEHICLE
) return;
420 VehicleID new_vehicle_over
= INVALID_VEHICLE
;
421 if (gdvp
.head
!= nullptr) {
422 if (gdvp
.wagon
== nullptr && gdvp
.head
->Last()->index
!= this->sel
) { // ..at the end of the train.
423 /* NOTE: As a wagon can't be moved at the begin of a train, head index isn't used to mark a drag-and-drop
424 * destination inside a train. This head index is then used to indicate that a wagon is inserted at
425 * the end of the train.
427 new_vehicle_over
= gdvp
.head
->index
;
428 } else if (gdvp
.wagon
!= nullptr && gdvp
.head
!= gdvp
.wagon
&&
429 gdvp
.wagon
->index
!= this->sel
&&
430 gdvp
.wagon
->Previous()->index
!= this->sel
) { // ..over an existing wagon.
431 new_vehicle_over
= gdvp
.wagon
->index
;
434 if (this->vehicle_over
== new_vehicle_over
) return;
436 this->vehicle_over
= new_vehicle_over
;
437 this->SetWidgetDirty(widget
);
440 virtual void OnPaint()
443 uint min_height
= 30;
446 CargoArray cargo_caps
;
448 if (virtual_train
!= nullptr) {
449 for (Train
*train
= virtual_train
; train
!= nullptr; train
= train
->Next()) {
450 width
+= train
->GetDisplayImageWidth();
451 cargo_caps
[train
->cargo_type
] += train
->cargo_cap
;
454 for (CargoID i
= 0; i
< NUM_CARGO
; ++i
) {
455 if ( cargo_caps
[i
] > 0 ) {
456 height
+= this->line_height
/3;
461 min_width
= max(min_width
, width
);
462 this->hscroll
->SetCount(min_width
+ 50);
464 min_height
= max(min_height
, height
);
465 this->vscroll
->SetCount(min_height
);
469 struct GetDepotVehiclePtData
{
471 const Vehicle
*wagon
;
474 enum DepotGUIAction
{
484 DepotGUIAction
GetVehicleFromDepotWndPt(int x
, int y
, const Vehicle
**veh
, GetDepotVehiclePtData
*d
) const
486 const NWidgetCore
*matrix_widget
= this->GetWidget
<NWidgetCore
>(TCW_NEW_TMPL_PANEL
);
487 /* In case of RTL the widgets are swapped as a whole */
488 if (_current_text_dir
== TD_RTL
) x
= matrix_widget
->current_x
- x
;
490 x
-= TRAIN_FRONT_SPACE
;
496 x
+= this->hscroll
->GetPosition();
497 const Train
*v
= virtual_train
;
498 d
->head
= d
->wagon
= v
;
500 if (xm
<= this->header_width
) {
502 if (wagon
) return MODE_ERROR
;
504 return MODE_SHOW_VEHICLE
;
507 /* Account for the header */
508 x
-= this->header_width
;
510 /* find the vehicle in this row that was clicked */
511 for (; v
!= nullptr; v
= v
->Next()) {
512 x
-= v
->GetDisplayImageWidth();
516 d
->wagon
= (v
!= nullptr ? v
->GetFirstEnginePart() : nullptr);
518 return MODE_DRAG_VEHICLE
;
521 void ClickedOnVehiclePanel(int x
, int y
)
523 GetDepotVehiclePtData gdvp
= { nullptr, nullptr };
524 const Vehicle
*v
= nullptr;
525 this->GetVehicleFromDepotWndPt(x
, y
, &v
, &gdvp
);
529 if (v
!= nullptr && VehicleClicked(v
)) return;
530 VehicleID sel
= this->sel
;
532 if (sel
!= INVALID_VEHICLE
) {
533 this->sel
= INVALID_VEHICLE
;
534 TrainDepotMoveVehicle(v
, sel
, gdvp
.head
);
535 } else if (v
!= nullptr) {
536 SetObjectToPlaceWnd(SPR_CURSOR_MOUSE
, PAL_NONE
, HT_DRAG
, this);
537 SetMouseCursorVehicle(v
, EIT_IN_DEPOT
);
539 this->sel
= v
->index
;
542 _cursor
.vehchain
= _ctrl_pressed
;
546 void RearrangeVirtualTrain()
548 virtual_train
= virtual_train
->First();
549 assert(HasBit(virtual_train
->subtype
, GVSF_VIRTUAL
));
552 void UpdateButtonState()
554 this->SetWidgetDisabledState(TCW_REFIT
, virtual_train
== nullptr);
558 void ShowTemplateCreateWindow(TemplateVehicle
*to_edit
, bool *create_window_open
, int step_h
)
560 if ( BringWindowToFrontById(WC_CREATE_TEMPLATE
, VEH_TRAIN
) != nullptr ) return;
561 new TemplateCreateWindow(&_template_create_window_desc
, to_edit
, create_window_open
, step_h
);
564 void CcSetVirtualTrain(const CommandCost
&result
, TileIndex tile
, uint32 p1
, uint32 p2
)
566 if (result
.Failed()) return;
568 Window
* window
= FindWindowById(WC_CREATE_TEMPLATE
, 0);
570 Train
* train
= Train::From(Vehicle::Get(_new_vehicle_id
));
571 ((TemplateCreateWindow
*)window
)->SetVirtualTrain(train
);
572 window
->InvalidateData();
576 void CcVirtualTrainWagonsMoved(const CommandCost
&result
, TileIndex tile
, uint32 p1
, uint32 p2
)
578 if (result
.Failed()) return;
580 Window
* window
= FindWindowById(WC_CREATE_TEMPLATE
, 0);
582 ((TemplateCreateWindow
*)window
)->RearrangeVirtualTrain();
583 window
->InvalidateData();
587 void CcDeleteVirtualTrain(const CommandCost
&result
, TileIndex tile
, uint32 p1
, uint32 p2
)
589 if (result
.Failed()) return;
591 Window
* window
= FindWindowById(WC_CREATE_TEMPLATE
, 0);
593 window
->InvalidateData();