Fix crash when setting separation mode for vehicles with no orders list.
[openttd-joker.git] / src / plans_gui.cpp
blob73d62410523ca8612be4bdd7760e54a0e2249828
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
10 /** @file plans_gui.cpp The GUI for planning. */
12 #include "stdafx.h"
13 #include "plans_func.h"
14 #include "plans_base.h"
15 #include "command_func.h"
16 #include "company_func.h"
17 #include "company_gui.h"
18 #include "settings_gui.h"
19 #include "window_gui.h"
20 #include "window_func.h"
21 #include "viewport_func.h"
22 #include "gfx_func.h"
23 #include "textbuf_gui.h"
24 #include "tilehighlight_func.h"
25 #include "strings_func.h"
26 #include "core/pool_func.hpp"
27 #include "widgets/plans_widget.h"
28 #include "table/strings.h"
29 #include "table/sprites.h"
31 static const NWidgetPart _nested_plans_widgets[] = {
32 NWidget(NWID_HORIZONTAL),
33 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
34 NWidget(WWT_CAPTION, COLOUR_GREY, WID_PLN_CAPTION), SetDataTip(STR_PLANS_CAPTION, STR_NULL),
35 NWidget(WWT_SHADEBOX, COLOUR_GREY),
36 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
37 NWidget(WWT_STICKYBOX, COLOUR_GREY),
38 EndContainer(),
40 NWidget(NWID_HORIZONTAL),
41 NWidget(WWT_PANEL, COLOUR_GREY),
42 NWidget(NWID_HORIZONTAL),
43 NWidget(WWT_INSET, COLOUR_GREY, WID_PLN_LIST), SetFill(1, 1), SetPadding(2, 1, 2, 2), SetResize(1, 0), SetScrollbar(WID_PLN_SCROLLBAR), SetDataTip(STR_NULL, STR_PLANS_LIST_TOOLTIP),
44 EndContainer(),
45 EndContainer(),
46 EndContainer(),
47 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_PLN_SCROLLBAR),
48 EndContainer(),
50 NWidget(WWT_PANEL, COLOUR_GREY),
51 NWidget(NWID_HORIZONTAL),
52 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_PLN_NEW), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_PLANS_NEW_PLAN, STR_NULL),
53 NWidget(WWT_TEXTBTN_2, COLOUR_GREY, WID_PLN_ADD_LINES), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_PLANS_ADD_LINES, STR_PLANS_ADDING_LINES),
54 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_PLN_VISIBILITY), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_PLANS_VISIBILITY_PUBLIC, STR_PLANS_VISIBILITY_TOOLTIP),
55 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_PLN_HIDE_ALL_SEL),
56 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_PLN_HIDE_ALL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_PLANS_HIDE_ALL, STR_PLANS_HIDE_ALL_TOOLTIP),
57 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_PLN_SHOW_ALL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_PLANS_SHOW_ALL, STR_PLANS_SHOW_ALL_TOOLTIP),
58 EndContainer(),
59 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_PLN_DELETE), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_PLANS_DELETE, STR_PLANS_DELETE_TOOLTIP),
60 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_PLN_RENAME), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_BUTTON_RENAME, STR_NULL),
61 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
62 EndContainer(),
63 EndContainer(),
66 static WindowDesc _plans_desc(
67 WDP_AUTO, "plans", 300, 100,
68 WC_PLANS, WC_NONE,
69 WDF_CONSTRUCTION,
70 _nested_plans_widgets, lengthof(_nested_plans_widgets)
73 struct PlansWindow : Window {
74 typedef struct {
75 bool is_plan;
76 int plan_id;
77 int line_id;
78 } ListItem;
80 Scrollbar *vscroll;
81 NWidgetStacked *hide_all_sel;
82 std::vector<ListItem> list; ///< The translation table linking panel indices to their related PlanID.
83 int selected; ///< What item is currently selected in the panel.
84 uint vis_btn_left; ///< left offset of visibility button
85 Dimension company_icon_spr_dim; ///< dimensions of company icon
87 PlansWindow(WindowDesc *desc) : Window(desc)
89 this->CreateNestedTree();
90 this->vscroll = this->GetScrollbar(WID_PLN_SCROLLBAR);
91 this->hide_all_sel = this->GetWidget<NWidgetStacked>(WID_PLN_HIDE_ALL_SEL);
92 this->hide_all_sel->SetDisplayedPlane(0);
93 this->FinishInitNested();
95 this->selected = INT_MAX;
96 RebuildList();
99 ~PlansWindow()
101 this->list.clear();
102 _current_plan = nullptr;
105 virtual void OnClick(Point pt, int widget, int click_count)
107 switch (widget) {
108 case WID_PLN_NEW:
109 DoCommandP(0, _local_company, 0, CMD_ADD_PLAN, CcAddPlan);
110 break;
111 case WID_PLN_ADD_LINES:
112 if (_current_plan) HandlePlacePushButton(this, widget, SPR_CURSOR_MOUSE, HT_POINT);
113 break;
114 case WID_PLN_DELETE:
115 if (this->selected != INT_MAX) {
116 if (this->list[this->selected].is_plan) {
117 DoCommandP(0, this->list[this->selected].plan_id, 0, CMD_REMOVE_PLAN);
119 else {
120 DoCommandP(0, this->list[this->selected].plan_id, this->list[this->selected].line_id, CMD_REMOVE_PLAN_LINE);
123 break;
125 case WID_PLN_RENAME: {
126 if (_current_plan != nullptr) {
127 SetDParamStr(0, _current_plan->GetNameCStr());
128 ShowQueryString(STR_JUST_RAW_STRING, STR_PLANS_QUERY_RENAME_PLAN,
129 MAX_LENGTH_PLAN_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_LEN_IN_CHARS);
131 break;
134 case WID_PLN_HIDE_ALL: {
135 Plan *p;
136 FOR_ALL_PLANS(p) {
137 if (p->IsListable()) p->SetVisibility(false);
139 this->SetWidgetDirty(WID_PLN_LIST);
140 break;
142 case WID_PLN_SHOW_ALL: {
143 Plan *p;
144 FOR_ALL_PLANS(p) {
145 if (p->IsListable()) p->SetVisibility(true);
147 this->SetWidgetDirty(WID_PLN_LIST);
148 break;
150 case WID_PLN_VISIBILITY:
151 if (_current_plan) _current_plan->ToggleVisibilityByAll();
152 break;
153 case WID_PLN_LIST: {
154 int new_selected = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_PLN_LIST, WD_FRAMERECT_TOP);
155 if (_ctrl_pressed) {
156 if (new_selected != INT_MAX) {
157 TileIndex t;
158 if (this->list[new_selected].is_plan) {
159 t = Plan::Get(this->list[new_selected].plan_id)->CalculateCentreTile();
160 } else {
161 t = Plan::Get(this->list[new_selected].plan_id)->lines[this->list[new_selected].line_id]->CalculateCentreTile();
163 if (t != INVALID_TILE) ScrollMainWindowToTile(t);
165 return;
167 if (this->selected != INT_MAX) {
168 _current_plan->SetFocus(false);
170 if (new_selected != INT_MAX) {
171 const int btn_left = this->vis_btn_left;
172 const int btn_right = btn_left + SETTING_BUTTON_WIDTH;
173 if (this->list[new_selected].is_plan) {
174 _current_plan = Plan::Get(this->list[new_selected].plan_id);
175 _current_plan->SetFocus(true);
176 if (pt.x >= btn_left && pt.x < btn_right) _current_plan->ToggleVisibility();
178 else {
179 _current_plan = Plan::Get(this->list[new_selected].plan_id);
180 PlanLine *pl = _current_plan->lines[this->list[new_selected].line_id];
181 pl->SetFocus(true);
182 if (pt.x >= btn_left && pt.x < btn_right) {
183 if (pl->ToggleVisibility()) _current_plan->SetVisibility(true, false);
186 if (click_count > 1 && (pt.x < 22 || pt.x >= 41)) {
187 _current_plan->show_lines = !_current_plan->show_lines;
188 this->InvalidateData(INVALID_PLAN);
191 else {
192 if (_current_plan) {
193 _current_plan->SetFocus(false);
194 _current_plan = nullptr;
197 this->selected = new_selected;
198 this->SetDirty();
199 break;
201 default: break;
205 virtual void OnQueryTextFinished(char *str)
207 if (_current_plan == nullptr || str == nullptr) return;
209 DoCommandP(0, _current_plan->index, 0, CMD_RENAME_PLAN | CMD_MSG(STR_ERROR_CAN_T_RENAME_PLAN), nullptr, str);
212 bool AllPlansHidden() const
214 Plan *p;
215 FOR_ALL_PLANS(p) {
216 if (p->IsVisible()) return false;
218 return true;
221 virtual void OnPaint()
223 this->SetWidgetDisabledState(WID_PLN_HIDE_ALL, this->vscroll->GetCount() == 0);
224 this->SetWidgetDisabledState(WID_PLN_SHOW_ALL, this->vscroll->GetCount() == 0);
225 this->hide_all_sel->SetDisplayedPlane(this->vscroll->GetCount() != 0 && this->AllPlansHidden() ? 1 : 0);
226 if (_current_plan) {
227 this->SetWidgetsDisabledState(_current_plan->owner != _local_company, WID_PLN_ADD_LINES, WID_PLN_VISIBILITY, WID_PLN_DELETE, WID_PLN_RENAME, WIDGET_LIST_END);
228 this->GetWidget<NWidgetCore>(WID_PLN_VISIBILITY)->widget_data = _current_plan->visible_by_all ? STR_PLANS_VISIBILITY_PRIVATE : STR_PLANS_VISIBILITY_PUBLIC;
230 else {
231 this->SetWidgetsDisabledState(true, WID_PLN_ADD_LINES, WID_PLN_VISIBILITY, WID_PLN_DELETE, WID_PLN_RENAME, WIDGET_LIST_END);
233 this->DrawWidgets();
236 virtual void DrawWidget(const Rect &r, int widget) const
238 switch (widget) {
239 case WID_PLN_LIST: {
240 uint y = r.top + WD_FRAMERECT_TOP; // Offset from top of widget.
241 if (this->vscroll->GetCount() == 0) {
242 DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, STR_STATION_LIST_NONE);
243 return;
246 bool rtl = _current_text_dir == TD_RTL;
247 uint icon_left = (rtl ? r.right - WD_FRAMERECT_RIGHT - this->company_icon_spr_dim.width : WD_FRAMETEXT_LEFT + r.left);
248 uint btn_left = (rtl ? icon_left - SETTING_BUTTON_WIDTH - 4 : icon_left + this->company_icon_spr_dim.width + 4);
249 uint text_left = (rtl ? r.left + WD_FRAMERECT_LEFT : btn_left + SETTING_BUTTON_WIDTH + 4);
250 uint text_right = (rtl ? btn_left - 4 : r.right - WD_FRAMERECT_RIGHT);
251 const_cast<PlansWindow*>(this)->vis_btn_left = btn_left;
253 for (uint16 i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < this->vscroll->GetCount(); i++) {
254 Plan *p = Plan::Get(list[i].plan_id);
256 if (i == this->selected) GfxFillRect(r.left + 1, y, r.right, y + this->resize.step_height, PC_DARK_GREY);
258 if (list[i].is_plan) {
259 DrawCompanyIcon(p->owner, icon_left, y + (this->resize.step_height - this->company_icon_spr_dim.height) / 2);
260 DrawBoolButton(btn_left, y + (this->resize.step_height - SETTING_BUTTON_HEIGHT) / 2, p->visible, true);
261 if (!p->HasName()) {
262 SetDParam(0, list[i].plan_id + 1);
263 } else {
264 SetDParamStr(0, p->GetNameCStr());
266 SetDParam(1, p->lines.size());
267 SetDParam(2, p->creation_date);
268 DrawString(text_left, text_right, y + (this->resize.step_height - FONT_HEIGHT_NORMAL) / 2, p->HasName() ? STR_PLANS_LIST_ITEM_NAMED_PLAN : STR_PLANS_LIST_ITEM_PLAN, p->visible_by_all ? TC_LIGHT_BLUE : TC_GOLD);
270 else {
271 PlanLine *pl = p->lines[list[i].line_id];
272 DrawBoolButton(btn_left, y + (this->resize.step_height - SETTING_BUTTON_HEIGHT) / 2, pl->visible, true);
273 SetDParam(0, list[i].line_id + 1);
274 SetDParam(1, pl->tiles.size() - 1);
275 DrawString(text_left, text_right, y + (this->resize.step_height - FONT_HEIGHT_NORMAL) / 2, STR_PLANS_LIST_ITEM_LINE, TC_BLACK);
277 y += this->resize.step_height;
279 break;
284 virtual void OnResize()
286 this->vscroll->SetCapacityFromWidget(this, WID_PLN_LIST, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM);
289 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
291 switch (widget) {
292 case WID_PLN_LIST:
293 this->company_icon_spr_dim = GetSpriteSize(SPR_COMPANY_ICON);
294 resize->height = max<int>(FONT_HEIGHT_NORMAL, SETTING_BUTTON_HEIGHT);
295 size->height = resize->height * 5 + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
296 break;
300 /** The drawing of a line starts. */
301 virtual void OnPlaceObject(Point pt, TileIndex tile)
303 /* A player can't add lines to a public plan of another company. */
304 if (_current_plan && _current_plan->owner == _local_company) VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_DRAW_PLANLINE);
307 /** The drawing of a line is in progress. */
308 virtual void OnPlaceDrag(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt)
310 const Point p = GetTileBelowCursor();
311 const TileIndex tile = TileVirtXY(p.x, p.y);
312 if (_current_plan && tile < MapSize()) {
313 _current_plan->StoreTempTile(tile);
314 _thd.selstart = _thd.selend;
318 /** The drawing of a line ends up normally. */
319 virtual void OnPlaceMouseUp(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt, TileIndex start_tile, TileIndex end_tile)
321 if (_current_plan) _current_plan->ValidateNewLine();
324 /** The drawing of a line is aborted. */
325 virtual void OnPlaceObjectAbort()
327 if (_current_plan) {
328 _current_plan->temp_line->MarkDirty();
329 _current_plan->temp_line->Clear();
332 this->RaiseWidget(WID_PLN_ADD_LINES);
333 this->SetWidgetDirty(WID_PLN_ADD_LINES);
336 void RebuildList()
338 int old_focused_plan_id = this->selected == INT_MAX ? INT_MAX : this->list[this->selected].plan_id;
340 int sbcnt = 0;
341 this->list.clear();
342 Plan *p;
343 FOR_ALL_PLANS(p) {
344 if (!p->IsListable()) continue;
346 ListItem li;
347 li.is_plan = true;
348 li.plan_id = p->index;
349 this->list.push_back(li);
350 if (old_focused_plan_id == p->index) this->selected = sbcnt;
351 sbcnt++;
353 if (p->show_lines) {
354 const int sz = (int)p->lines.size();
355 sbcnt += sz;
356 li.is_plan = false;
357 for (int i = 0; i < sz; i++) {
358 li.line_id = i;
359 this->list.push_back(li);
364 if (this->selected == INT_MAX) ResetObjectToPlace();
366 this->vscroll->SetCount(sbcnt);
369 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
371 if (data != INVALID_PLAN && this->selected != INT_MAX) {
372 if (this->list[this->selected].plan_id == data) {
373 /* Invalidate the selection if the selected plan has been modified or deleted. */
374 this->selected = INT_MAX;
376 /* Cancel drawing associated to the deleted plan. */
377 ResetObjectToPlace();
381 RebuildList();
384 void SelectPlan(PlanID plan_index)
386 if (this->selected != INT_MAX) {
387 if (plan_index == this->list[this->selected].plan_id) return;
388 Plan::Get(this->list[this->selected].plan_id)->SetFocus(false);
391 if (plan_index == INVALID_PLAN) {
392 this->selected = INT_MAX;
393 return;
395 Plan::Get(plan_index)->SetFocus(true);
397 for (size_t i = 0; i < this->list.size(); i++) {
398 if (this->list[i].is_plan && this->list[i].plan_id == plan_index) {
399 this->selected = (int)i;
400 return;
406 /** Show the window to manage plans. */
407 void ShowPlansWindow()
409 if (BringWindowToFrontById(WC_PLANS, 0) != nullptr) return;
410 new PlansWindow(&_plans_desc);
414 * Only the creator of a plan executes this function.
415 * The other players should not be bothered with these changes.
417 void CcAddPlan(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
419 if (result.Failed()) return;
421 _current_plan = _new_plan;
422 _current_plan->SetVisibility(true);
424 Window *w = FindWindowById(WC_PLANS, 0);
425 if (w) {
426 w->InvalidateData(INVALID_PLAN, false);
427 ((PlansWindow *)w)->SelectPlan(_current_plan->index);
428 if (!w->IsWidgetLowered(WID_PLN_ADD_LINES)) {
429 w->SetWidgetDisabledState(WID_PLN_ADD_LINES, false);
430 HandlePlacePushButton(w, WID_PLN_ADD_LINES, SPR_CURSOR_MOUSE, HT_POINT);