1 /*
2  * This file is part of OpenTTD.
3  * 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.
4  * 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.
5  * 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/>.
6  */
7 
8 /** @file bootstrap_gui.cpp Barely used user interface for bootstrapping OpenTTD, i.e. downloading the required content. */
9 
10 #include "stdafx.h"
11 #include "base_media_base.h"
12 #include "blitter/factory.hpp"
13 
14 #if defined(WITH_FREETYPE) || defined(WITH_UNISCRIBE) || defined(WITH_COCOA)
15 
16 #include "core/geometry_func.hpp"
17 #include "error.h"
18 #include "fontcache.h"
19 #include "gfx_func.h"
20 #include "network/network.h"
21 #include "network/network_content_gui.h"
22 #include "openttd.h"
23 #include "strings_func.h"
24 #include "video/video_driver.hpp"
25 #include "window_func.h"
26 
27 #include "widgets/bootstrap_widget.h"
28 
29 #include "table/strings.h"
30 
31 #include "safeguards.h"
32 
33 /** Widgets for the background window to prevent smearing. */
34 static const struct NWidgetPart _background_widgets[] = {
35 	NWidget(WWT_PANEL, COLOUR_DARK_BLUE, WID_BB_BACKGROUND), SetResize(1, 1),
36 };
37 
38 /**
39  * Window description for the background window to prevent smearing.
40  */
41 static WindowDesc _background_desc(
42 	WDP_MANUAL, nullptr, 0, 0,
43 	WC_BOOTSTRAP, WC_NONE,
44 	0,
45 	_background_widgets, lengthof(_background_widgets)
46 );
47 
48 /** The background for the game. */
49 class BootstrapBackground : public Window {
50 public:
BootstrapBackground()51 	BootstrapBackground() : Window(&_background_desc)
52 	{
53 		this->InitNested(0);
54 		CLRBITS(this->flags, WF_WHITE_BORDER);
55 		ResizeWindow(this, _screen.width, _screen.height);
56 	}
57 
DrawWidget(const Rect & r,int widget) const58 	void DrawWidget(const Rect &r, int widget) const override
59 	{
60 		GfxFillRect(r.left, r.top, r.right, r.bottom, 4, FILLRECT_OPAQUE);
61 		GfxFillRect(r.left, r.top, r.right, r.bottom, 0, FILLRECT_CHECKER);
62 	}
63 };
64 
65 /** Nested widgets for the error window. */
66 static const NWidgetPart _nested_bootstrap_errmsg_widgets[] = {
67 	NWidget(NWID_HORIZONTAL),
68 		NWidget(WWT_CAPTION, COLOUR_GREY, WID_BEM_CAPTION), SetDataTip(STR_MISSING_GRAPHICS_ERROR_TITLE, STR_NULL),
69 	EndContainer(),
70 	NWidget(WWT_PANEL, COLOUR_GREY),
71 		NWidget(WWT_PANEL, COLOUR_GREY, WID_BEM_MESSAGE), EndContainer(),
72 		NWidget(NWID_HORIZONTAL),
73 			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BEM_QUIT), SetDataTip(STR_MISSING_GRAPHICS_ERROR_QUIT, STR_NULL), SetFill(1, 0),
74 		EndContainer(),
75 	EndContainer(),
76 };
77 
78 /** Window description for the error window. */
79 static WindowDesc _bootstrap_errmsg_desc(
80 	WDP_CENTER, nullptr, 0, 0,
81 	WC_BOOTSTRAP, WC_NONE,
82 	WDF_MODAL,
83 	_nested_bootstrap_errmsg_widgets, lengthof(_nested_bootstrap_errmsg_widgets)
84 );
85 
86 /** The window for a failed bootstrap. */
87 class BootstrapErrorWindow : public Window {
88 public:
BootstrapErrorWindow()89 	BootstrapErrorWindow() : Window(&_bootstrap_errmsg_desc)
90 	{
91 		this->InitNested(1);
92 	}
93 
Close()94 	void Close() override
95 	{
96 		_exit_game = true;
97 		this->Window::Close();
98 	}
99 
UpdateWidgetSize(int widget,Dimension * size,const Dimension & padding,Dimension * fill,Dimension * resize)100 	void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
101 	{
102 		if (widget == WID_BEM_MESSAGE) {
103 			*size = GetStringBoundingBox(STR_MISSING_GRAPHICS_ERROR);
104 			size->height = GetStringHeight(STR_MISSING_GRAPHICS_ERROR, size->width - WD_FRAMETEXT_LEFT - WD_FRAMETEXT_RIGHT) + WD_FRAMETEXT_BOTTOM + WD_FRAMETEXT_TOP;
105 		}
106 	}
107 
DrawWidget(const Rect & r,int widget) const108 	void DrawWidget(const Rect &r, int widget) const override
109 	{
110 		if (widget == WID_BEM_MESSAGE) {
111 			DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMETEXT_TOP, r.bottom - WD_FRAMETEXT_BOTTOM, STR_MISSING_GRAPHICS_ERROR, TC_FROMSTRING, SA_CENTER);
112 		}
113 	}
114 
OnClick(Point pt,int widget,int click_count)115 	void OnClick(Point pt, int widget, int click_count) override
116 	{
117 		if (widget == WID_BEM_QUIT) {
118 			_exit_game = true;
119 		}
120 	}
121 };
122 
123 /** Nested widgets for the download window. */
124 static const NWidgetPart _nested_bootstrap_download_status_window_widgets[] = {
125 	NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_CONTENT_DOWNLOAD_TITLE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
126 	NWidget(WWT_PANEL, COLOUR_GREY, WID_NCDS_BACKGROUND),
127 		NWidget(NWID_SPACER), SetMinimalSize(350, 0), SetMinimalTextLines(3, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 30),
128 	EndContainer(),
129 };
130 
131 /** Window description for the download window */
132 static WindowDesc _bootstrap_download_status_window_desc(
133 	WDP_CENTER, nullptr, 0, 0,
134 	WC_NETWORK_STATUS_WINDOW, WC_NONE,
135 	WDF_MODAL,
136 	_nested_bootstrap_download_status_window_widgets, lengthof(_nested_bootstrap_download_status_window_widgets)
137 );
138 
139 
140 /** Window for showing the download status of content */
141 struct BootstrapContentDownloadStatusWindow : public BaseNetworkContentDownloadStatusWindow {
142 public:
143 	/** Simple call the constructor of the superclass. */
BootstrapContentDownloadStatusWindowBootstrapContentDownloadStatusWindow144 	BootstrapContentDownloadStatusWindow() : BaseNetworkContentDownloadStatusWindow(&_bootstrap_download_status_window_desc)
145 	{
146 	}
147 
CloseBootstrapContentDownloadStatusWindow148 	void Close() override
149 	{
150 		/* If we are not set to exit the game, it means the bootstrap failed. */
151 		if (!_exit_game) {
152 			new BootstrapErrorWindow();
153 		}
154 		this->BaseNetworkContentDownloadStatusWindow::Close();
155 	}
156 
OnDownloadCompleteBootstrapContentDownloadStatusWindow157 	void OnDownloadComplete(ContentID cid) override
158 	{
159 		/* We have completed downloading. We can trigger finding the right set now. */
160 		BaseGraphics::FindSets();
161 
162 		/* And continue going into the menu. */
163 		_game_mode = GM_MENU;
164 
165 		/* _exit_game is used to break out of the outer video driver's MainLoop. */
166 		_exit_game = true;
167 		this->Close();
168 	}
169 };
170 
171 /** The widgets for the query. It has no close box as that sprite does not exist yet. */
172 static const NWidgetPart _bootstrap_query_widgets[] = {
173 	NWidget(NWID_HORIZONTAL),
174 		NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_MISSING_GRAPHICS_SET_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
175 	EndContainer(),
176 	NWidget(WWT_PANEL, COLOUR_GREY),
177 		NWidget(WWT_PANEL, COLOUR_GREY, WID_BAFD_QUESTION), EndContainer(),
178 		NWidget(NWID_HORIZONTAL),
179 			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BAFD_YES), SetDataTip(STR_MISSING_GRAPHICS_YES_DOWNLOAD, STR_NULL),
180 			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BAFD_NO), SetDataTip(STR_MISSING_GRAPHICS_NO_QUIT, STR_NULL),
181 		EndContainer(),
182 	EndContainer(),
183 };
184 
185 /** The window description for the query. */
186 static WindowDesc _bootstrap_query_desc(
187 	WDP_CENTER, nullptr, 0, 0,
188 	WC_CONFIRM_POPUP_QUERY, WC_NONE,
189 	0,
190 	_bootstrap_query_widgets, lengthof(_bootstrap_query_widgets)
191 );
192 
193 /** The window for the query. It can't use the generic query window as that uses sprites that don't exist yet. */
194 class BootstrapAskForDownloadWindow : public Window, ContentCallback {
195 	Dimension button_size; ///< The dimension of the button
196 
197 public:
198 	/** Start listening to the content client events. */
BootstrapAskForDownloadWindow()199 	BootstrapAskForDownloadWindow() : Window(&_bootstrap_query_desc)
200 	{
201 		this->InitNested(WN_CONFIRM_POPUP_QUERY_BOOTSTRAP);
202 		_network_content_client.AddCallback(this);
203 	}
204 
205 	/** Stop listening to the content client events. */
Close()206 	void Close() override
207 	{
208 		_network_content_client.RemoveCallback(this);
209 		this->Window::Close();
210 	}
211 
UpdateWidgetSize(int widget,Dimension * size,const Dimension & padding,Dimension * fill,Dimension * resize)212 	void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
213 	{
214 		/* We cache the button size. This is safe as no reinit can happen here. */
215 		if (this->button_size.width == 0) {
216 			this->button_size = maxdim(GetStringBoundingBox(STR_MISSING_GRAPHICS_YES_DOWNLOAD), GetStringBoundingBox(STR_MISSING_GRAPHICS_NO_QUIT));
217 			this->button_size.width += WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT;
218 			this->button_size.height += WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM;
219 		}
220 
221 		switch (widget) {
222 			case WID_BAFD_QUESTION:
223 				/* The question is twice as wide as the buttons, and determine the height based on the width. */
224 				size->width = this->button_size.width * 2;
225 				size->height = GetStringHeight(STR_MISSING_GRAPHICS_SET_MESSAGE, size->width - WD_FRAMETEXT_LEFT - WD_FRAMETEXT_RIGHT) + WD_FRAMETEXT_BOTTOM + WD_FRAMETEXT_TOP;
226 				break;
227 
228 			case WID_BAFD_YES:
229 			case WID_BAFD_NO:
230 				*size = this->button_size;
231 				break;
232 		}
233 	}
234 
DrawWidget(const Rect & r,int widget) const235 	void DrawWidget(const Rect &r, int widget) const override
236 	{
237 		if (widget != 0) return;
238 
239 		DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMETEXT_TOP, r.bottom - WD_FRAMETEXT_BOTTOM, STR_MISSING_GRAPHICS_SET_MESSAGE, TC_FROMSTRING, SA_CENTER);
240 	}
241 
OnClick(Point pt,int widget,int click_count)242 	void OnClick(Point pt, int widget, int click_count) override
243 	{
244 		switch (widget) {
245 			case WID_BAFD_YES:
246 				/* We got permission to connect! Yay! */
247 				_network_content_client.Connect();
248 				break;
249 
250 			case WID_BAFD_NO:
251 				_exit_game = true;
252 				break;
253 
254 			default:
255 				break;
256 		}
257 	}
258 
OnConnect(bool success)259 	void OnConnect(bool success) override
260 	{
261 		/* Once connected, request the metadata. */
262 		_network_content_client.RequestContentList(CONTENT_TYPE_BASE_GRAPHICS);
263 	}
264 
OnReceiveContentInfo(const ContentInfo * ci)265 	void OnReceiveContentInfo(const ContentInfo *ci) override
266 	{
267 		/* And once the meta data is received, start downloading it. */
268 		_network_content_client.Select(ci->id);
269 		new BootstrapContentDownloadStatusWindow();
270 		this->Close();
271 	}
272 };
273 
274 #endif /* defined(WITH_FREETYPE) */
275 
276 /**
277  * Handle all procedures for bootstrapping OpenTTD without a base graphics set.
278  * This requires all kinds of trickery that is needed to avoid the use of
279  * sprites from the base graphics set which are pretty interwoven.
280  * @return True if a base set exists, otherwise false.
281  */
HandleBootstrap()282 bool HandleBootstrap()
283 {
284 	if (BaseGraphics::GetUsedSet() != nullptr) return true;
285 
286 	/* No user interface, bail out with an error. */
287 	if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) goto failure;
288 
289 	/* If there is no network or no freetype, then there is nothing we can do. Go straight to failure. */
290 #if (defined(_WIN32) && defined(WITH_UNISCRIBE)) || (defined(WITH_FREETYPE) && (defined(WITH_FONTCONFIG) || defined(__APPLE__))) || defined(WITH_COCOA)
291 	if (!_network_available) goto failure;
292 
293 	/* First tell the game we're bootstrapping. */
294 	_game_mode = GM_BOOTSTRAP;
295 
296 	/* Initialise the freetype font code. */
297 	InitializeUnicodeGlyphMap();
298 	/* Next "force" finding a suitable freetype font as the local font is missing. */
299 	CheckForMissingGlyphs(false);
300 
301 	/* Initialise the palette. The biggest step is 'faking' some recolour sprites.
302 	 * This way the mauve and gray colours work and we can show the user interface. */
303 	GfxInitPalettes();
304 	static const int offsets[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x80, 0, 0, 0, 0x04, 0x08 };
305 	for (uint i = 0; i != 16; i++) {
306 		for (int j = 0; j < 8; j++) {
307 			_colour_gradient[i][j] = offsets[i] + j;
308 		}
309 	}
310 
311 	/* Finally ask the question. */
312 	new BootstrapBackground();
313 	new BootstrapAskForDownloadWindow();
314 
315 	/* Process the user events. */
316 	VideoDriver::GetInstance()->MainLoop();
317 
318 	/* _exit_game is used to get out of the video driver's main loop.
319 	 * In case GM_BOOTSTRAP is still set we did not exit it via the
320 	 * "download complete" event, so it was a manual exit. Obey it. */
321 	_exit_game = _game_mode == GM_BOOTSTRAP;
322 	if (_exit_game) return false;
323 
324 	/* Try to probe the graphics. Should work this time. */
325 	if (!BaseGraphics::SetSet({})) goto failure;
326 
327 	/* Finally we can continue heading for the menu. */
328 	_game_mode = GM_MENU;
329 	return true;
330 #endif
331 
332 	/* Failure to get enough working to get a graphics set. */
333 failure:
334 	usererror("Failed to find a graphics set. Please acquire a graphics set for OpenTTD. See section 1.4 of README.md.");
335 	return false;
336 }
337