Update readme and changelog for v1.27.0
[openttd-joker.git] / src / tbtr_template_gui_create.cpp
blob831cb0e6785eccc3ee565ec6b0fc92981a4c588c
1 #include "stdafx.h"
3 #include "gfx_func.h"
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"
16 #include "gui.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"
30 #include "group.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"
37 #include "train.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 {
45 TCW_CAPTION,
46 TCW_NEW_TMPL_PANEL,
47 TCW_INFO_PANEL,
48 TCW_SCROLLBAR_H_NEW_TMPL,
49 TCW_SCROLLBAR_V_NEW_TMPL,
50 TCW_SELL_TMPL,
51 TCW_NEW,
52 TCW_OK,
53 TCW_CANCEL,
54 TCW_REFIT,
55 TCW_CLONE,
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),
65 EndContainer(),
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),
71 EndContainer(),
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),
74 EndContainer(),
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),
82 EndContainer(),
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();
105 } else {
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 {
116 private:
117 Scrollbar *hscroll;
118 Scrollbar *vscroll;
119 int line_height;
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)
122 VehicleID sel;
123 VehicleID vehicle_over;
124 bool sell_hovered; ///< A vehicle is being dragged/hovered over the sell button.
125 uint32 template_index;
127 public:
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);
135 /* a sprite */
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;
153 UpdateButtonState();
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);
166 /* more cleanup */
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));
182 UpdateButtonState();
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) {
201 delete this;
202 return;
205 this->SetDirty();
206 UpdateButtonState();
209 virtual void OnClick(Point pt, int widget, int click_count)
211 switch(widget) {
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);
215 break;
217 case TCW_NEW: {
218 ShowTemplateTrainBuildVehicleWindow(&virtual_train);
219 break;
221 case TCW_CLONE: {
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);
227 } else {
228 ResetObjectToPlace();
230 break;
232 case TCW_OK: {
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);
239 delete this;
240 break;
242 case TCW_CANCEL: {
243 delete this;
244 break;
246 case TCW_REFIT: {
247 if (virtual_train != nullptr) {
248 ShowVehicleRefitWindow(virtual_train, INVALID_VEH_ORDER_ID, this, false, true);
250 break;
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;
263 // create a new one
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();
267 this->SetDirty();
269 return true;
272 virtual void OnPlaceObjectAbort()
274 this->sel = INVALID_VEHICLE;
275 this->vehicle_over = INVALID_VEHICLE;
276 this->RaiseButtons();
277 this->SetDirty();
280 virtual void DrawWidget(const Rect &r, int widget) const
282 switch(widget) {
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));
287 SetDParam(1, 1);
288 DrawString(r.left, r.right, r.top, STR_TINY_BLACK_DECIMAL, TC_BLACK, SA_RIGHT);
290 break;
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;
298 old_dpi = _cur_dpi;
299 _cur_dpi = &tmp_dpi;
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 ) {
327 SetDParam(0, i);
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;
335 _cur_dpi = old_dpi;
337 break;
339 default:
340 break;
344 virtual void OnDragDrop(Point pt, int widget)
346 switch (widget) {
347 case TCW_NEW_TMPL_PANEL: {
348 const Vehicle *v = nullptr;
349 VehicleID sel = this->sel;
351 this->sel = INVALID_VEHICLE;
352 this->SetDirty();
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);
363 break;
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;
380 this->SetDirty();
381 UpdateButtonState();
382 break;
384 default:
385 this->sel = INVALID_VEHICLE;
386 this->SetDirty();
387 break;
389 this->sell_hovered = false;
390 _cursor.vehchain = false;
391 this->sel = INVALID_VEHICLE;
392 this->SetDirty();
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);
412 return;
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()
442 uint min_width = 32;
443 uint min_height = 30;
444 uint width = 0;
445 uint 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);
467 this->DrawWidgets();
469 struct GetDepotVehiclePtData {
470 const Vehicle *head;
471 const Vehicle *wagon;
474 enum DepotGUIAction {
475 MODE_ERROR,
476 MODE_DRAG_VEHICLE,
477 MODE_SHOW_VEHICLE,
478 MODE_START_STOP,
481 uint count_width;
482 uint header_width;
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;
492 uint xm = x;
494 bool wagon = false;
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();
513 if (x < 0) break;
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);
527 v = gdvp.wagon;
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;
540 this->SetDirty();
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);
569 if (window) {
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);
581 if (window) {
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);
592 if (window) {
593 window->InvalidateData();