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/>.
10 /** @file plans_gui.cpp The GUI for planning. */
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"
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
),
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
),
47 NWidget(NWID_VSCROLLBAR
, COLOUR_GREY
, WID_PLN_SCROLLBAR
),
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
),
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
),
66 static WindowDesc
_plans_desc(
67 WDP_AUTO
, "plans", 300, 100,
70 _nested_plans_widgets
, lengthof(_nested_plans_widgets
)
73 struct PlansWindow
: Window
{
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
;
102 _current_plan
= nullptr;
105 virtual void OnClick(Point pt
, int widget
, int click_count
)
109 DoCommandP(0, _local_company
, 0, CMD_ADD_PLAN
, CcAddPlan
);
111 case WID_PLN_ADD_LINES
:
112 if (_current_plan
) HandlePlacePushButton(this, widget
, SPR_CURSOR_MOUSE
, HT_POINT
);
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
);
120 DoCommandP(0, this->list
[this->selected
].plan_id
, this->list
[this->selected
].line_id
, CMD_REMOVE_PLAN_LINE
);
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
);
134 case WID_PLN_HIDE_ALL
: {
137 if (p
->IsListable()) p
->SetVisibility(false);
139 this->SetWidgetDirty(WID_PLN_LIST
);
142 case WID_PLN_SHOW_ALL
: {
145 if (p
->IsListable()) p
->SetVisibility(true);
147 this->SetWidgetDirty(WID_PLN_LIST
);
150 case WID_PLN_VISIBILITY
:
151 if (_current_plan
) _current_plan
->ToggleVisibilityByAll();
154 int new_selected
= this->vscroll
->GetScrolledRowFromWidget(pt
.y
, this, WID_PLN_LIST
, WD_FRAMERECT_TOP
);
156 if (new_selected
!= INT_MAX
) {
158 if (this->list
[new_selected
].is_plan
) {
159 t
= Plan::Get(this->list
[new_selected
].plan_id
)->CalculateCentreTile();
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
);
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();
179 _current_plan
= Plan::Get(this->list
[new_selected
].plan_id
);
180 PlanLine
*pl
= _current_plan
->lines
[this->list
[new_selected
].line_id
];
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
);
193 _current_plan
->SetFocus(false);
194 _current_plan
= nullptr;
197 this->selected
= new_selected
;
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
216 if (p
->IsVisible()) return false;
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);
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
;
231 this->SetWidgetsDisabledState(true, WID_PLN_ADD_LINES
, WID_PLN_VISIBILITY
, WID_PLN_DELETE
, WID_PLN_RENAME
, WIDGET_LIST_END
);
236 virtual void DrawWidget(const Rect
&r
, int widget
) const
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
);
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);
262 SetDParam(0, list
[i
].plan_id
+ 1);
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
);
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
;
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
)
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
;
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()
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
);
338 int old_focused_plan_id
= this->selected
== INT_MAX
? INT_MAX
: this->list
[this->selected
].plan_id
;
344 if (!p
->IsListable()) continue;
348 li
.plan_id
= p
->index
;
349 this->list
.push_back(li
);
350 if (old_focused_plan_id
== p
->index
) this->selected
= sbcnt
;
354 const int sz
= (int)p
->lines
.size();
357 for (int i
= 0; i
< sz
; 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();
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
;
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
;
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);
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
);