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;
104 if (head
!= NULL
) wagon
= head
->Last();
106 wagon
= wagon
->Previous();
107 if (wagon
== NULL
) return;
110 if (wagon
== v
) return;
112 DoCommandP(v
->tile
, v
->index
| (_ctrl_pressed
? 1 : 0) << 20 | 1 << 21, wagon
== NULL
? 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
!= NULL
);
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
!= NULL
) ? 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
!= NULL
) {
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
);
166 *create_window_open
= false;
167 DeleteWindowById(WC_BUILD_VIRTUAL_TRAIN
, this->window_number
);
168 InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN
);
171 void SetVirtualTrain(Train
* const train
)
173 if (virtual_train
!= nullptr) {
174 DoCommandP(0, virtual_train
->index
, 0, CMD_DELETE_VIRTUAL_TRAIN
);
177 virtual_train
= train
;
178 if (virtual_train
!= NULL
) {
179 assert(HasBit(virtual_train
->subtype
, GVSF_VIRTUAL
));
184 virtual void OnResize()
186 NWidgetCore
*template_panel
= this->GetWidget
<NWidgetCore
>(TCW_NEW_TMPL_PANEL
);
187 this->hscroll
->SetCapacity(template_panel
->current_x
);
189 NWidgetCore
*info_panel
= this->GetWidget
<NWidgetCore
>(TCW_INFO_PANEL
);
190 this->vscroll
->SetCapacity(info_panel
->current_y
);
194 virtual void OnInvalidateData(int data
= 0, bool gui_scope
= true)
196 if(!gui_scope
) return;
198 if (this->template_index
!= INVALID_VEHICLE
) {
199 if (TemplateVehicle::GetIfValid(this->template_index
) == NULL
) {
208 virtual void OnClick(Point pt
, int widget
, int click_count
)
211 case TCW_NEW_TMPL_PANEL
: {
212 NWidgetBase
*nwi
= this->GetWidget
<NWidgetBase
>(TCW_NEW_TMPL_PANEL
);
213 ClickedOnVehiclePanel(pt
.x
- nwi
->pos_x
, pt
.y
- nwi
->pos_y
);
217 ShowTemplateTrainBuildVehicleWindow(&virtual_train
);
221 this->SetWidgetDirty(TCW_CLONE
);
222 this->ToggleWidgetLoweredState(TCW_CLONE
);
223 if (this->IsWidgetLowered(TCW_CLONE
)) {
224 static const CursorID clone_icon
= SPR_CURSOR_CLONE_TRAIN
;
225 SetObjectToPlaceWnd(clone_icon
, PAL_NONE
, HT_VEHICLE
, this);
227 ResetObjectToPlace();
232 if (virtual_train
!= nullptr) {
233 DoCommandP(0, this->template_index
, virtual_train
->index
, CMD_REPLACE_TEMPLATE_VEHICLE
);
234 virtual_train
= nullptr;
235 } else if (this->template_index
!= INVALID_VEHICLE
) {
236 DoCommandP(0, this->template_index
, 0, CMD_DELETE_TEMPLATE_VEHICLE
);
246 if (virtual_train
!= nullptr) {
247 ShowVehicleRefitWindow(virtual_train
, INVALID_VEH_ORDER_ID
, this, false, true);
254 virtual bool OnVehicleSelect(const Vehicle
*v
)
256 // throw away the current virtual train
257 if (virtual_train
!= nullptr) {
258 DoCommandP(0, virtual_train
->index
, 0, CMD_DELETE_VIRTUAL_TRAIN
);
259 virtual_train
= nullptr;
263 DoCommandP(0, v
->index
, 0, CMD_VIRTUAL_TRAIN_FROM_TRAIN
| CMD_MSG(STR_TMPL_CANT_CREATE
), CcSetVirtualTrain
);
264 this->ToggleWidgetLoweredState(TCW_CLONE
);
265 ResetObjectToPlace();
271 virtual void OnPlaceObjectAbort()
273 this->sel
= INVALID_VEHICLE
;
274 this->vehicle_over
= INVALID_VEHICLE
;
275 this->RaiseButtons();
279 virtual void DrawWidget(const Rect
&r
, int widget
) const
282 case TCW_NEW_TMPL_PANEL
: {
283 if ( this->virtual_train
) {
284 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
);
285 SetDParam(0, CeilDiv(virtual_train
->gcache
.cached_total_length
* 10, TILE_SIZE
));
287 DrawString(r
.left
, r
.right
, r
.top
, STR_TINY_BLACK_DECIMAL
, TC_BLACK
, SA_RIGHT
);
291 case TCW_INFO_PANEL
: {
292 if ( this->virtual_train
) {
293 DrawPixelInfo tmp_dpi
, *old_dpi
;
295 if (!FillDrawPixelInfo(&tmp_dpi
, r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
)) break;
300 /* Draw vehicle performance info */
302 uint16 loaded_weight
= 0;
304 for (const Vehicle
*u
= this->virtual_train
; u
!= NULL
; u
= u
->Next()) {
305 loaded_weight
+= Train::From(u
)->GetLoadedWeight();
308 const GroundVehicleCache
*gcache
= this->virtual_train
->GetGroundVehicleCache();
309 double tractive_effort_loaded
= loaded_weight
* this->virtual_train
->GetRollingFriction();
310 double power
= gcache
->cached_power
* 746ll;
311 double max_te
= gcache
->cached_max_te
;
312 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
)));
314 SetDParam(0, loaded_weight
);
315 SetDParam(1, gcache
->cached_power
);
316 SetDParam(2, loaded_max_speed
);
317 SetDParam(3, gcache
->cached_max_te
/ 1000);
318 DrawString(8, r
.right
, 4 - this->vscroll
->GetPosition(), STR_VEHICLE_INFO_LOADED_WEIGHT_POWER_MAX_SPEED_MAX_TE
);
319 /* Draw cargo summary */
320 CargoArray cargo_caps
;
321 for ( const Train
*tmp
=this->virtual_train
; tmp
; tmp
=tmp
->Next() )
322 cargo_caps
[tmp
->cargo_type
] += tmp
->cargo_cap
;
323 int y
= 30 - this->vscroll
->GetPosition();
324 for (CargoID i
= 0; i
< NUM_CARGO
; ++i
) {
325 if ( cargo_caps
[i
] > 0 ) {
327 SetDParam(1, cargo_caps
[i
]);
328 SetDParam(2, _settings_game
.vehicle
.freight_trains
);
329 DrawString(8, r
.right
, y
, STR_TMPL_CARGO_SUMMARY
, TC_LIGHT_BLUE
, SA_LEFT
);
330 y
+= this->line_height
/3;
343 virtual void OnDragDrop(Point pt
, int widget
)
346 case TCW_NEW_TMPL_PANEL
: {
347 const Vehicle
*v
= NULL
;
348 VehicleID sel
= this->sel
;
350 this->sel
= INVALID_VEHICLE
;
353 NWidgetBase
*nwi
= this->GetWidget
<NWidgetBase
>(TCW_NEW_TMPL_PANEL
);
354 GetDepotVehiclePtData gdvp
= { NULL
, NULL
};
356 if (this->GetVehicleFromDepotWndPt(pt
.x
- nwi
->pos_x
, pt
.y
- nwi
->pos_y
, &v
, &gdvp
) == MODE_DRAG_VEHICLE
&& sel
!= INVALID_VEHICLE
) {
357 if (gdvp
.wagon
== NULL
|| gdvp
.wagon
->index
!= sel
) {
358 this->vehicle_over
= INVALID_VEHICLE
;
359 TrainDepotMoveVehicle(gdvp
.wagon
, sel
, gdvp
.head
);
364 case TCW_SELL_TMPL
: {
365 if (this->IsWidgetDisabled(widget
)) return;
366 if (this->sel
== INVALID_VEHICLE
) return;
368 int sell_cmd
= (_ctrl_pressed
) ? 1 : 0;
370 Train
* train_to_delete
= Train::Get(this->sel
);
372 if (virtual_train
== train_to_delete
)
373 virtual_train
= (_ctrl_pressed
) ? nullptr : virtual_train
->GetNextUnit();
375 DoCommandP(0, this->sel
| sell_cmd
<< 20 | 1 << 21, 0, GetCmdSellVeh(VEH_TRAIN
), CcDeleteVirtualTrain
);
377 this->sel
= INVALID_VEHICLE
;
384 this->sel
= INVALID_VEHICLE
;
388 this->sell_hovered
= false;
389 _cursor
.vehchain
= false;
390 this->sel
= INVALID_VEHICLE
;
394 virtual void OnMouseDrag(Point pt
, int widget
)
396 if (this->sel
== INVALID_VEHICLE
) return;
398 bool is_sell_widget
= widget
== TCW_SELL_TMPL
;
399 if (is_sell_widget
!= this->sell_hovered
) {
400 this->sell_hovered
= is_sell_widget
;
401 this->SetWidgetLoweredState(TCW_SELL_TMPL
, is_sell_widget
);
402 this->SetWidgetDirty(TCW_SELL_TMPL
);
405 /* A rail vehicle is dragged.. */
406 if (widget
!= TCW_NEW_TMPL_PANEL
) { // ..outside of the depot matrix.
407 if (this->vehicle_over
!= INVALID_VEHICLE
) {
408 this->vehicle_over
= INVALID_VEHICLE
;
409 this->SetWidgetDirty(TCW_NEW_TMPL_PANEL
);
414 NWidgetBase
*matrix
= this->GetWidget
<NWidgetBase
>(widget
);
415 const Vehicle
*v
= NULL
;
416 GetDepotVehiclePtData gdvp
= {NULL
, NULL
};
418 if (this->GetVehicleFromDepotWndPt(pt
.x
- matrix
->pos_x
, pt
.y
- matrix
->pos_y
, &v
, &gdvp
) != MODE_DRAG_VEHICLE
) return;
419 VehicleID new_vehicle_over
= INVALID_VEHICLE
;
420 if (gdvp
.head
!= NULL
) {
421 if (gdvp
.wagon
== NULL
&& gdvp
.head
->Last()->index
!= this->sel
) { // ..at the end of the train.
422 /* 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
423 * destination inside a train. This head index is then used to indicate that a wagon is inserted at
424 * the end of the train.
426 new_vehicle_over
= gdvp
.head
->index
;
427 } else if (gdvp
.wagon
!= NULL
&& gdvp
.head
!= gdvp
.wagon
&&
428 gdvp
.wagon
->index
!= this->sel
&&
429 gdvp
.wagon
->Previous()->index
!= this->sel
) { // ..over an existing wagon.
430 new_vehicle_over
= gdvp
.wagon
->index
;
433 if (this->vehicle_over
== new_vehicle_over
) return;
435 this->vehicle_over
= new_vehicle_over
;
436 this->SetWidgetDirty(widget
);
439 virtual void OnPaint()
442 uint min_height
= 30;
445 CargoArray cargo_caps
;
447 if (virtual_train
!= nullptr) {
448 for (Train
*train
= virtual_train
; train
!= nullptr; train
= train
->Next()) {
449 width
+= train
->GetDisplayImageWidth();
450 cargo_caps
[train
->cargo_type
] += train
->cargo_cap
;
453 for (CargoID i
= 0; i
< NUM_CARGO
; ++i
) {
454 if ( cargo_caps
[i
] > 0 ) {
455 height
+= this->line_height
/3;
460 min_width
= max(min_width
, width
);
461 this->hscroll
->SetCount(min_width
+ 50);
463 min_height
= max(min_height
, height
);
464 this->vscroll
->SetCount(min_height
);
468 struct GetDepotVehiclePtData
{
470 const Vehicle
*wagon
;
473 enum DepotGUIAction
{
483 DepotGUIAction
GetVehicleFromDepotWndPt(int x
, int y
, const Vehicle
**veh
, GetDepotVehiclePtData
*d
) const
485 const NWidgetCore
*matrix_widget
= this->GetWidget
<NWidgetCore
>(TCW_NEW_TMPL_PANEL
);
486 /* In case of RTL the widgets are swapped as a whole */
487 if (_current_text_dir
== TD_RTL
) x
= matrix_widget
->current_x
- x
;
489 x
-= TRAIN_FRONT_SPACE
;
495 x
+= this->hscroll
->GetPosition();
496 const Train
*v
= virtual_train
;
497 d
->head
= d
->wagon
= v
;
499 if (xm
<= this->header_width
) {
501 if (wagon
) return MODE_ERROR
;
503 return MODE_SHOW_VEHICLE
;
506 /* Account for the header */
507 x
-= this->header_width
;
509 /* find the vehicle in this row that was clicked */
510 for (; v
!= NULL
; v
= v
->Next()) {
511 x
-= v
->GetDisplayImageWidth();
515 d
->wagon
= (v
!= NULL
? v
->GetFirstEnginePart() : NULL
);
517 return MODE_DRAG_VEHICLE
;
520 void ClickedOnVehiclePanel(int x
, int y
)
522 GetDepotVehiclePtData gdvp
= { NULL
, NULL
};
523 const Vehicle
*v
= NULL
;
524 this->GetVehicleFromDepotWndPt(x
, y
, &v
, &gdvp
);
528 if (v
!= NULL
&& VehicleClicked(v
)) return;
529 VehicleID sel
= this->sel
;
531 if (sel
!= INVALID_VEHICLE
) {
532 this->sel
= INVALID_VEHICLE
;
533 TrainDepotMoveVehicle(v
, sel
, gdvp
.head
);
534 } else if (v
!= NULL
) {
535 SetObjectToPlaceWnd(SPR_CURSOR_MOUSE
, PAL_NONE
, HT_DRAG
, this);
536 SetMouseCursorVehicle(v
, EIT_IN_DEPOT
);
538 this->sel
= v
->index
;
541 _cursor
.vehchain
= _ctrl_pressed
;
545 void RearrangeVirtualTrain()
547 virtual_train
= virtual_train
->First();
548 assert(HasBit(virtual_train
->subtype
, GVSF_VIRTUAL
));
551 void UpdateButtonState()
553 this->SetWidgetDisabledState(TCW_REFIT
, virtual_train
== NULL
);
557 void ShowTemplateCreateWindow(TemplateVehicle
*to_edit
, bool *create_window_open
, int step_h
)
559 if ( BringWindowToFrontById(WC_CREATE_TEMPLATE
, VEH_TRAIN
) != NULL
) return;
560 new TemplateCreateWindow(&_template_create_window_desc
, to_edit
, create_window_open
, step_h
);
563 void CcSetVirtualTrain(const CommandCost
&result
, TileIndex tile
, uint32 p1
, uint32 p2
)
565 if (result
.Failed()) return;
567 Window
* window
= FindWindowById(WC_CREATE_TEMPLATE
, 0);
569 Train
* train
= Train::From(Vehicle::Get(_new_vehicle_id
));
570 ((TemplateCreateWindow
*)window
)->SetVirtualTrain(train
);
571 window
->InvalidateData();
575 void CcVirtualTrainWagonsMoved(const CommandCost
&result
, TileIndex tile
, uint32 p1
, uint32 p2
)
577 if (result
.Failed()) return;
579 Window
* window
= FindWindowById(WC_CREATE_TEMPLATE
, 0);
581 ((TemplateCreateWindow
*)window
)->RearrangeVirtualTrain();
582 window
->InvalidateData();
586 void CcDeleteVirtualTrain(const CommandCost
&result
, TileIndex tile
, uint32 p1
, uint32 p2
)
588 if (result
.Failed()) return;
590 Window
* window
= FindWindowById(WC_CREATE_TEMPLATE
, 0);
592 window
->InvalidateData();