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
;
181 virtual void OnResize()
183 NWidgetCore
*template_panel
= this->GetWidget
<NWidgetCore
>(TCW_NEW_TMPL_PANEL
);
184 this->hscroll
->SetCapacity(template_panel
->current_x
);
186 NWidgetCore
*info_panel
= this->GetWidget
<NWidgetCore
>(TCW_INFO_PANEL
);
187 this->vscroll
->SetCapacity(info_panel
->current_y
);
191 virtual void OnInvalidateData(int data
= 0, bool gui_scope
= true)
193 if(!gui_scope
) return;
195 if (this->template_index
!= INVALID_VEHICLE
) {
196 if (TemplateVehicle::GetIfValid(this->template_index
) == NULL
) {
205 virtual void OnClick(Point pt
, int widget
, int click_count
)
208 case TCW_NEW_TMPL_PANEL
: {
209 NWidgetBase
*nwi
= this->GetWidget
<NWidgetBase
>(TCW_NEW_TMPL_PANEL
);
210 ClickedOnVehiclePanel(pt
.x
- nwi
->pos_x
, pt
.y
- nwi
->pos_y
);
214 ShowTemplateTrainBuildVehicleWindow(&virtual_train
);
218 this->SetWidgetDirty(TCW_CLONE
);
219 this->ToggleWidgetLoweredState(TCW_CLONE
);
220 if (this->IsWidgetLowered(TCW_CLONE
)) {
221 static const CursorID clone_icon
= SPR_CURSOR_CLONE_TRAIN
;
222 SetObjectToPlaceWnd(clone_icon
, PAL_NONE
, HT_VEHICLE
, this);
224 ResetObjectToPlace();
229 if (virtual_train
!= nullptr) {
230 DoCommandP(0, this->template_index
, virtual_train
->index
, CMD_REPLACE_TEMPLATE_VEHICLE
);
231 virtual_train
= nullptr;
232 } else if (this->template_index
!= INVALID_VEHICLE
) {
233 DoCommandP(0, this->template_index
, 0, CMD_DELETE_TEMPLATE_VEHICLE
);
243 if (virtual_train
!= nullptr) {
244 ShowVehicleRefitWindow(virtual_train
, INVALID_VEH_ORDER_ID
, this, false, true);
251 virtual bool OnVehicleSelect(const Vehicle
*v
)
253 // throw away the current virtual train
254 if (virtual_train
!= nullptr) {
255 DoCommandP(0, virtual_train
->index
, 0, CMD_DELETE_VIRTUAL_TRAIN
);
256 virtual_train
= nullptr;
260 DoCommandP(0, v
->index
, 0, CMD_VIRTUAL_TRAIN_FROM_TRAIN
| CMD_MSG(STR_TMPL_CANT_CREATE
), CcSetVirtualTrain
);
261 this->ToggleWidgetLoweredState(TCW_CLONE
);
262 ResetObjectToPlace();
268 virtual void OnPlaceObjectAbort()
270 this->sel
= INVALID_VEHICLE
;
271 this->vehicle_over
= INVALID_VEHICLE
;
272 this->RaiseButtons();
276 virtual void DrawWidget(const Rect
&r
, int widget
) const
279 case TCW_NEW_TMPL_PANEL
: {
280 if ( this->virtual_train
) {
281 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
);
282 SetDParam(0, CeilDiv(virtual_train
->gcache
.cached_total_length
* 10, TILE_SIZE
));
284 DrawString(r
.left
, r
.right
, r
.top
, STR_TINY_BLACK_DECIMAL
, TC_BLACK
, SA_RIGHT
);
288 case TCW_INFO_PANEL
: {
289 if ( this->virtual_train
) {
290 DrawPixelInfo tmp_dpi
, *old_dpi
;
292 if (!FillDrawPixelInfo(&tmp_dpi
, r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
)) break;
297 /* Draw vehicle performance info */
299 uint16 loaded_weight
= 0;
301 for (const Vehicle
*u
= this->virtual_train
; u
!= NULL
; u
= u
->Next()) {
302 loaded_weight
+= Train::From(u
)->GetLoadedWeight();
305 const GroundVehicleCache
*gcache
= this->virtual_train
->GetGroundVehicleCache();
306 double tractive_effort_loaded
= loaded_weight
* this->virtual_train
->GetRollingFriction();
307 double power
= gcache
->cached_power
* 746ll;
308 double max_te
= gcache
->cached_max_te
;
309 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
)));
311 SetDParam(0, loaded_weight
);
312 SetDParam(1, gcache
->cached_power
);
313 SetDParam(2, loaded_max_speed
);
314 SetDParam(3, gcache
->cached_max_te
/ 1000);
315 DrawString(8, r
.right
, 4 - this->vscroll
->GetPosition(), STR_VEHICLE_INFO_LOADED_WEIGHT_POWER_MAX_SPEED_MAX_TE
);
316 /* Draw cargo summary */
317 CargoArray cargo_caps
;
318 for ( const Train
*tmp
=this->virtual_train
; tmp
; tmp
=tmp
->Next() )
319 cargo_caps
[tmp
->cargo_type
] += tmp
->cargo_cap
;
320 int y
= 30 - this->vscroll
->GetPosition();
321 for (CargoID i
= 0; i
< NUM_CARGO
; ++i
) {
322 if ( cargo_caps
[i
] > 0 ) {
324 SetDParam(1, cargo_caps
[i
]);
325 SetDParam(2, _settings_game
.vehicle
.freight_trains
);
326 DrawString(8, r
.right
, y
, STR_TMPL_CARGO_SUMMARY
, TC_LIGHT_BLUE
, SA_LEFT
);
327 y
+= this->line_height
/3;
340 virtual void OnDragDrop(Point pt
, int widget
)
343 case TCW_NEW_TMPL_PANEL
: {
344 const Vehicle
*v
= NULL
;
345 VehicleID sel
= this->sel
;
347 this->sel
= INVALID_VEHICLE
;
350 NWidgetBase
*nwi
= this->GetWidget
<NWidgetBase
>(TCW_NEW_TMPL_PANEL
);
351 GetDepotVehiclePtData gdvp
= { NULL
, NULL
};
353 if (this->GetVehicleFromDepotWndPt(pt
.x
- nwi
->pos_x
, pt
.y
- nwi
->pos_y
, &v
, &gdvp
) == MODE_DRAG_VEHICLE
&& sel
!= INVALID_VEHICLE
) {
354 if (gdvp
.wagon
== NULL
|| gdvp
.wagon
->index
!= sel
) {
355 this->vehicle_over
= INVALID_VEHICLE
;
356 TrainDepotMoveVehicle(gdvp
.wagon
, sel
, gdvp
.head
);
361 case TCW_SELL_TMPL
: {
362 if (this->IsWidgetDisabled(widget
)) return;
363 if (this->sel
== INVALID_VEHICLE
) return;
365 int sell_cmd
= (_ctrl_pressed
) ? 1 : 0;
367 Train
* train_to_delete
= Train::Get(this->sel
);
369 if (virtual_train
== train_to_delete
)
370 virtual_train
= (_ctrl_pressed
) ? nullptr : virtual_train
->GetNextUnit();
372 DoCommandP(0, this->sel
| sell_cmd
<< 20 | 1 << 21, 0, GetCmdSellVeh(VEH_TRAIN
), CcDeleteVirtualTrain
);
374 this->sel
= INVALID_VEHICLE
;
381 this->sel
= INVALID_VEHICLE
;
385 this->sell_hovered
= false;
386 _cursor
.vehchain
= false;
387 this->sel
= INVALID_VEHICLE
;
391 virtual void OnMouseDrag(Point pt
, int widget
)
393 if (this->sel
== INVALID_VEHICLE
) return;
395 bool is_sell_widget
= widget
== TCW_SELL_TMPL
;
396 if (is_sell_widget
!= this->sell_hovered
) {
397 this->sell_hovered
= is_sell_widget
;
398 this->SetWidgetLoweredState(TCW_SELL_TMPL
, is_sell_widget
);
399 this->SetWidgetDirty(TCW_SELL_TMPL
);
402 /* A rail vehicle is dragged.. */
403 if (widget
!= TCW_NEW_TMPL_PANEL
) { // ..outside of the depot matrix.
404 if (this->vehicle_over
!= INVALID_VEHICLE
) {
405 this->vehicle_over
= INVALID_VEHICLE
;
406 this->SetWidgetDirty(TCW_NEW_TMPL_PANEL
);
411 NWidgetBase
*matrix
= this->GetWidget
<NWidgetBase
>(widget
);
412 const Vehicle
*v
= NULL
;
413 GetDepotVehiclePtData gdvp
= {NULL
, NULL
};
415 if (this->GetVehicleFromDepotWndPt(pt
.x
- matrix
->pos_x
, pt
.y
- matrix
->pos_y
, &v
, &gdvp
) != MODE_DRAG_VEHICLE
) return;
416 VehicleID new_vehicle_over
= INVALID_VEHICLE
;
417 if (gdvp
.head
!= NULL
) {
418 if (gdvp
.wagon
== NULL
&& gdvp
.head
->Last()->index
!= this->sel
) { // ..at the end of the train.
419 /* 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
420 * destination inside a train. This head index is then used to indicate that a wagon is inserted at
421 * the end of the train.
423 new_vehicle_over
= gdvp
.head
->index
;
424 } else if (gdvp
.wagon
!= NULL
&& gdvp
.head
!= gdvp
.wagon
&&
425 gdvp
.wagon
->index
!= this->sel
&&
426 gdvp
.wagon
->Previous()->index
!= this->sel
) { // ..over an existing wagon.
427 new_vehicle_over
= gdvp
.wagon
->index
;
430 if (this->vehicle_over
== new_vehicle_over
) return;
432 this->vehicle_over
= new_vehicle_over
;
433 this->SetWidgetDirty(widget
);
436 virtual void OnPaint()
439 uint min_height
= 30;
442 CargoArray cargo_caps
;
444 if (virtual_train
!= nullptr) {
445 for (Train
*train
= virtual_train
; train
!= nullptr; train
= train
->Next()) {
446 width
+= train
->GetDisplayImageWidth();
447 cargo_caps
[train
->cargo_type
] += train
->cargo_cap
;
450 for (CargoID i
= 0; i
< NUM_CARGO
; ++i
) {
451 if ( cargo_caps
[i
] > 0 ) {
452 height
+= this->line_height
/3;
457 min_width
= max(min_width
, width
);
458 this->hscroll
->SetCount(min_width
+ 50);
460 min_height
= max(min_height
, height
);
461 this->vscroll
->SetCount(min_height
);
465 struct GetDepotVehiclePtData
{
467 const Vehicle
*wagon
;
470 enum DepotGUIAction
{
480 DepotGUIAction
GetVehicleFromDepotWndPt(int x
, int y
, const Vehicle
**veh
, GetDepotVehiclePtData
*d
) const
482 const NWidgetCore
*matrix_widget
= this->GetWidget
<NWidgetCore
>(TCW_NEW_TMPL_PANEL
);
483 /* In case of RTL the widgets are swapped as a whole */
484 if (_current_text_dir
== TD_RTL
) x
= matrix_widget
->current_x
- x
;
486 x
-= TRAIN_FRONT_SPACE
;
492 x
+= this->hscroll
->GetPosition();
493 const Train
*v
= virtual_train
;
494 d
->head
= d
->wagon
= v
;
496 if (xm
<= this->header_width
) {
498 if (wagon
) return MODE_ERROR
;
500 return MODE_SHOW_VEHICLE
;
503 /* Account for the header */
504 x
-= this->header_width
;
506 /* find the vehicle in this row that was clicked */
507 for (; v
!= NULL
; v
= v
->Next()) {
508 x
-= v
->GetDisplayImageWidth();
512 d
->wagon
= (v
!= NULL
? v
->GetFirstEnginePart() : NULL
);
514 return MODE_DRAG_VEHICLE
;
517 void ClickedOnVehiclePanel(int x
, int y
)
519 GetDepotVehiclePtData gdvp
= { NULL
, NULL
};
520 const Vehicle
*v
= NULL
;
521 this->GetVehicleFromDepotWndPt(x
, y
, &v
, &gdvp
);
525 if (v
!= NULL
&& VehicleClicked(v
)) return;
526 VehicleID sel
= this->sel
;
528 if (sel
!= INVALID_VEHICLE
) {
529 this->sel
= INVALID_VEHICLE
;
530 TrainDepotMoveVehicle(v
, sel
, gdvp
.head
);
531 } else if (v
!= NULL
) {
532 SetObjectToPlaceWnd(SPR_CURSOR_MOUSE
, PAL_NONE
, HT_DRAG
, this);
533 SetMouseCursorVehicle(v
, EIT_IN_DEPOT
);
535 this->sel
= v
->index
;
538 _cursor
.vehchain
= _ctrl_pressed
;
542 void RearrangeVirtualTrain()
544 virtual_train
= virtual_train
->First();
547 void UpdateButtonState()
549 this->SetWidgetDisabledState(TCW_REFIT
, virtual_train
== NULL
);
553 void ShowTemplateCreateWindow(TemplateVehicle
*to_edit
, bool *create_window_open
, int step_h
)
555 if ( BringWindowToFrontById(WC_CREATE_TEMPLATE
, VEH_TRAIN
) != NULL
) return;
556 new TemplateCreateWindow(&_template_create_window_desc
, to_edit
, create_window_open
, step_h
);
559 void CcSetVirtualTrain(const CommandCost
&result
, TileIndex tile
, uint32 p1
, uint32 p2
)
561 if (result
.Failed()) return;
563 Window
* window
= FindWindowById(WC_CREATE_TEMPLATE
, 0);
565 Train
* train
= Train::From(Vehicle::Get(_new_vehicle_id
));
566 ((TemplateCreateWindow
*)window
)->SetVirtualTrain(train
);
567 window
->InvalidateData();
571 void CcVirtualTrainWagonsMoved(const CommandCost
&result
, TileIndex tile
, uint32 p1
, uint32 p2
)
573 if (result
.Failed()) return;
575 Window
* window
= FindWindowById(WC_CREATE_TEMPLATE
, 0);
577 ((TemplateCreateWindow
*)window
)->RearrangeVirtualTrain();
578 window
->InvalidateData();
582 void CcDeleteVirtualTrain(const CommandCost
&result
, TileIndex tile
, uint32 p1
, uint32 p2
)
584 if (result
.Failed()) return;
586 Window
* window
= FindWindowById(WC_CREATE_TEMPLATE
, 0);
588 window
->InvalidateData();