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