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 main_gui.cpp Handling of the main viewport. */
9 
10 #include "stdafx.h"
11 #include "currency.h"
12 #include "spritecache.h"
13 #include "window_gui.h"
14 #include "window_func.h"
15 #include "textbuf_gui.h"
16 #include "viewport_func.h"
17 #include "command_func.h"
18 #include "console_gui.h"
19 #include "progress.h"
20 #include "transparency_gui.h"
21 #include "map_func.h"
22 #include "sound_func.h"
23 #include "transparency.h"
24 #include "strings_func.h"
25 #include "zoom_func.h"
26 #include "company_base.h"
27 #include "company_func.h"
28 #include "toolbar_gui.h"
29 #include "statusbar_gui.h"
30 #include "linkgraph/linkgraph_gui.h"
31 #include "tilehighlight_func.h"
32 #include "hotkeys.h"
33 #include "guitimer_func.h"
34 #include "error.h"
35 #include "news_gui.h"
36 
37 #include "saveload/saveload.h"
38 
39 #include "widgets/main_widget.h"
40 
41 #include "network/network.h"
42 #include "network/network_func.h"
43 #include "network/network_gui.h"
44 #include "network/network_base.h"
45 
46 #include "table/sprites.h"
47 #include "table/strings.h"
48 
49 #include "safeguards.h"
50 
51 /**
52  * This code is shared for the majority of the pushbuttons.
53  * Handles e.g. the pressing of a button (to build things), playing of click sound and sets certain parameters
54  *
55  * @param w Window which called the function
56  * @param widget ID of the widget (=button) that called this function
57  * @param cursor How should the cursor image change? E.g. cursor with depot image in it
58  * @param mode Tile highlighting mode, e.g. drawing a rectangle or a dot on the ground
59  * @return true if the button is clicked, false if it's unclicked
60  */
HandlePlacePushButton(Window * w,int widget,CursorID cursor,HighLightStyle mode)61 bool HandlePlacePushButton(Window *w, int widget, CursorID cursor, HighLightStyle mode)
62 {
63 	if (w->IsWidgetDisabled(widget)) return false;
64 
65 	if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
66 	w->SetDirty();
67 
68 	if (w->IsWidgetLowered(widget)) {
69 		ResetObjectToPlace();
70 		return false;
71 	}
72 
73 	SetObjectToPlace(cursor, PAL_NONE, mode, w->window_class, w->window_number);
74 	w->LowerWidget(widget);
75 	return true;
76 }
77 
78 
CcPlaySound_EXPLOSION(const CommandCost & result,TileIndex tile,uint32 p1,uint32 p2,uint32 cmd)79 void CcPlaySound_EXPLOSION(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd)
80 {
81 	if (result.Succeeded() && _settings_client.sound.confirm) SndPlayTileFx(SND_12_EXPLOSION, tile);
82 }
83 
84 /**
85  * Zooms a viewport in a window in or out.
86  * @param how Zooming direction.
87  * @param w   Window owning the viewport.
88  * @return Returns \c true if zooming step could be done, \c false if further zooming is not possible.
89  * @note No button handling or what so ever is done.
90  */
DoZoomInOutWindow(ZoomStateChange how,Window * w)91 bool DoZoomInOutWindow(ZoomStateChange how, Window *w)
92 {
93 	Viewport *vp;
94 
95 	assert(w != nullptr);
96 	vp = w->viewport;
97 
98 	switch (how) {
99 		case ZOOM_NONE:
100 			/* On initialisation of the viewport we don't do anything. */
101 			break;
102 
103 		case ZOOM_IN:
104 			if (vp->zoom <= _settings_client.gui.zoom_min) return false;
105 			vp->zoom = (ZoomLevel)((int)vp->zoom - 1);
106 			vp->virtual_width >>= 1;
107 			vp->virtual_height >>= 1;
108 
109 			w->viewport->scrollpos_x += vp->virtual_width >> 1;
110 			w->viewport->scrollpos_y += vp->virtual_height >> 1;
111 			w->viewport->dest_scrollpos_x = w->viewport->scrollpos_x;
112 			w->viewport->dest_scrollpos_y = w->viewport->scrollpos_y;
113 			w->viewport->follow_vehicle = INVALID_VEHICLE;
114 			break;
115 		case ZOOM_OUT:
116 			if (vp->zoom >= _settings_client.gui.zoom_max) return false;
117 			vp->zoom = (ZoomLevel)((int)vp->zoom + 1);
118 
119 			w->viewport->scrollpos_x -= vp->virtual_width >> 1;
120 			w->viewport->scrollpos_y -= vp->virtual_height >> 1;
121 			w->viewport->dest_scrollpos_x = w->viewport->scrollpos_x;
122 			w->viewport->dest_scrollpos_y = w->viewport->scrollpos_y;
123 
124 			vp->virtual_width <<= 1;
125 			vp->virtual_height <<= 1;
126 			w->viewport->follow_vehicle = INVALID_VEHICLE;
127 			break;
128 	}
129 	if (vp != nullptr) { // the vp can be null when how == ZOOM_NONE
130 		vp->virtual_left = w->viewport->scrollpos_x;
131 		vp->virtual_top = w->viewport->scrollpos_y;
132 	}
133 	/* Update the windows that have zoom-buttons to perhaps disable their buttons */
134 	w->InvalidateData();
135 	return true;
136 }
137 
ZoomInOrOutToCursorWindow(bool in,Window * w)138 void ZoomInOrOutToCursorWindow(bool in, Window *w)
139 {
140 	assert(w != nullptr);
141 
142 	if (_game_mode != GM_MENU) {
143 		Viewport *vp = w->viewport;
144 		if ((in && vp->zoom <= _settings_client.gui.zoom_min) || (!in && vp->zoom >= _settings_client.gui.zoom_max)) return;
145 
146 		Point pt = GetTileZoomCenterWindow(in, w);
147 		if (pt.x != -1) {
148 			ScrollWindowTo(pt.x, pt.y, -1, w, true);
149 
150 			DoZoomInOutWindow(in ? ZOOM_IN : ZOOM_OUT, w);
151 		}
152 	}
153 }
154 
FixTitleGameZoom(int zoom_adjust)155 void FixTitleGameZoom(int zoom_adjust)
156 {
157 	if (_game_mode != GM_MENU) return;
158 
159 	Viewport *vp = FindWindowByClass(WC_MAIN_WINDOW)->viewport;
160 
161 	/* Adjust the zoom in/out.
162 	 * Can't simply add, since operator+ is not defined on the ZoomLevel type. */
163 	vp->zoom = _gui_zoom;
164 	while (zoom_adjust < 0 && vp->zoom != _settings_client.gui.zoom_min) {
165 		vp->zoom--;
166 		zoom_adjust++;
167 	}
168 	while (zoom_adjust > 0 && vp->zoom != _settings_client.gui.zoom_max) {
169 		vp->zoom++;
170 		zoom_adjust--;
171 	}
172 
173 	vp->virtual_width = ScaleByZoom(vp->width, vp->zoom);
174 	vp->virtual_height = ScaleByZoom(vp->height, vp->zoom);
175 }
176 
177 static const struct NWidgetPart _nested_main_window_widgets[] = {
178 	NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_M_VIEWPORT), SetResize(1, 1),
179 };
180 
181 enum {
182 	GHK_QUIT,
183 	GHK_ABANDON,
184 	GHK_CONSOLE,
185 	GHK_BOUNDING_BOXES,
186 	GHK_DIRTY_BLOCKS,
187 	GHK_CENTER,
188 	GHK_CENTER_ZOOM,
189 	GHK_RESET_OBJECT_TO_PLACE,
190 	GHK_DELETE_WINDOWS,
191 	GHK_DELETE_NONVITAL_WINDOWS,
192 	GHK_DELETE_ALL_MESSAGES,
193 	GHK_REFRESH_SCREEN,
194 	GHK_CRASH,
195 	GHK_MONEY,
196 	GHK_UPDATE_COORDS,
197 	GHK_TOGGLE_TRANSPARENCY,
198 	GHK_TOGGLE_INVISIBILITY = GHK_TOGGLE_TRANSPARENCY + 9,
199 	GHK_TRANSPARENCY_TOOLBAR = GHK_TOGGLE_INVISIBILITY + 8,
200 	GHK_TRANSPARANCY,
201 	GHK_CHAT,
202 	GHK_CHAT_ALL,
203 	GHK_CHAT_COMPANY,
204 	GHK_CHAT_SERVER,
205 	GHK_CLOSE_NEWS,
206 	GHK_CLOSE_ERROR,
207 };
208 
209 struct MainWindow : Window
210 {
211 	GUITimer refresh;
212 
213 	/* Refresh times in milliseconds */
214 	static const uint LINKGRAPH_REFRESH_PERIOD = 7650;
215 	static const uint LINKGRAPH_DELAY = 450;
216 
MainWindowMainWindow217 	MainWindow(WindowDesc *desc) : Window(desc)
218 	{
219 		this->InitNested(0);
220 		CLRBITS(this->flags, WF_WHITE_BORDER);
221 		ResizeWindow(this, _screen.width, _screen.height);
222 
223 		NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_M_VIEWPORT);
224 		nvp->InitializeViewport(this, TileXY(32, 32), ZOOM_LVL_VIEWPORT);
225 
226 		this->viewport->overlay = new LinkGraphOverlay(this, WID_M_VIEWPORT, 0, 0, 3);
227 		this->refresh.SetInterval(LINKGRAPH_DELAY);
228 	}
229 
OnRealtimeTickMainWindow230 	void OnRealtimeTick(uint delta_ms) override
231 	{
232 		if (!this->refresh.Elapsed(delta_ms)) return;
233 
234 		this->refresh.SetInterval(LINKGRAPH_REFRESH_PERIOD);
235 
236 		if (this->viewport->overlay->GetCargoMask() == 0 ||
237 				this->viewport->overlay->GetCompanyMask() == 0) {
238 			return;
239 		}
240 
241 		this->viewport->overlay->SetDirty();
242 		this->GetWidget<NWidgetBase>(WID_M_VIEWPORT)->SetDirty(this);
243 	}
244 
OnPaintMainWindow245 	void OnPaint() override
246 	{
247 		this->DrawWidgets();
248 		if (_game_mode == GM_MENU) {
249 			static const SpriteID title_sprites[] = {SPR_OTTD_O, SPR_OTTD_P, SPR_OTTD_E, SPR_OTTD_N, SPR_OTTD_T, SPR_OTTD_T, SPR_OTTD_D};
250 			static const uint LETTER_SPACING = 10;
251 			int name_width = (lengthof(title_sprites) - 1) * LETTER_SPACING;
252 
253 			for (uint i = 0; i < lengthof(title_sprites); i++) {
254 				name_width += GetSpriteSize(title_sprites[i]).width;
255 			}
256 			int off_x = (this->width - name_width) / 2;
257 
258 			for (uint i = 0; i < lengthof(title_sprites); i++) {
259 				DrawSprite(title_sprites[i], PAL_NONE, off_x, 50);
260 				off_x += GetSpriteSize(title_sprites[i]).width + LETTER_SPACING;
261 			}
262 		}
263 	}
264 
OnHotkeyMainWindow265 	EventState OnHotkey(int hotkey) override
266 	{
267 		if (hotkey == GHK_QUIT) {
268 			HandleExitGameRequest();
269 			return ES_HANDLED;
270 		}
271 
272 		/* Disable all key shortcuts, except quit shortcuts when
273 		 * generating the world, otherwise they create threading
274 		 * problem during the generating, resulting in random
275 		 * assertions that are hard to trigger and debug */
276 		if (HasModalProgress()) return ES_NOT_HANDLED;
277 
278 		switch (hotkey) {
279 			case GHK_ABANDON:
280 				/* No point returning from the main menu to itself */
281 				if (_game_mode == GM_MENU) return ES_HANDLED;
282 				if (_settings_client.gui.autosave_on_exit) {
283 					DoExitSave();
284 					_switch_mode = SM_MENU;
285 				} else {
286 					AskExitToGameMenu();
287 				}
288 				return ES_HANDLED;
289 
290 			case GHK_CONSOLE:
291 				IConsoleSwitch();
292 				return ES_HANDLED;
293 
294 			case GHK_BOUNDING_BOXES:
295 				ToggleBoundingBoxes();
296 				return ES_HANDLED;
297 
298 			case GHK_DIRTY_BLOCKS:
299 				ToggleDirtyBlocks();
300 				return ES_HANDLED;
301 		}
302 
303 		if (_game_mode == GM_MENU) return ES_NOT_HANDLED;
304 
305 		switch (hotkey) {
306 			case GHK_CENTER:
307 			case GHK_CENTER_ZOOM: {
308 				Point pt = GetTileBelowCursor();
309 				if (pt.x != -1) {
310 					bool instant = (hotkey == GHK_CENTER_ZOOM && this->viewport->zoom != _settings_client.gui.zoom_min);
311 					if (hotkey == GHK_CENTER_ZOOM) MaxZoomInOut(ZOOM_IN, this);
312 					ScrollMainWindowTo(pt.x, pt.y, -1, instant);
313 				}
314 				break;
315 			}
316 
317 			case GHK_RESET_OBJECT_TO_PLACE: ResetObjectToPlace(); break;
318 			case GHK_DELETE_WINDOWS: CloseNonVitalWindows(); break;
319 			case GHK_DELETE_NONVITAL_WINDOWS: CloseAllNonVitalWindows(); break;
320 			case GHK_DELETE_ALL_MESSAGES: DeleteAllMessages(); break;
321 			case GHK_REFRESH_SCREEN: MarkWholeScreenDirty(); break;
322 
323 			case GHK_CRASH: // Crash the game
324 				*(volatile byte *)nullptr = 0;
325 				break;
326 
327 			case GHK_MONEY: // Gimme money
328 				/* You can only cheat for money in singleplayer mode. */
329 				if (!_networking) DoCommandP(0, 10000000, 0, CMD_MONEY_CHEAT);
330 				break;
331 
332 			case GHK_UPDATE_COORDS: // Update the coordinates of all station signs
333 				UpdateAllVirtCoords();
334 				break;
335 
336 			case GHK_TOGGLE_TRANSPARENCY:
337 			case GHK_TOGGLE_TRANSPARENCY + 1:
338 			case GHK_TOGGLE_TRANSPARENCY + 2:
339 			case GHK_TOGGLE_TRANSPARENCY + 3:
340 			case GHK_TOGGLE_TRANSPARENCY + 4:
341 			case GHK_TOGGLE_TRANSPARENCY + 5:
342 			case GHK_TOGGLE_TRANSPARENCY + 6:
343 			case GHK_TOGGLE_TRANSPARENCY + 7:
344 			case GHK_TOGGLE_TRANSPARENCY + 8:
345 				/* Transparency toggle hot keys */
346 				ToggleTransparency((TransparencyOption)(hotkey - GHK_TOGGLE_TRANSPARENCY));
347 				MarkWholeScreenDirty();
348 				break;
349 
350 			case GHK_TOGGLE_INVISIBILITY:
351 			case GHK_TOGGLE_INVISIBILITY + 1:
352 			case GHK_TOGGLE_INVISIBILITY + 2:
353 			case GHK_TOGGLE_INVISIBILITY + 3:
354 			case GHK_TOGGLE_INVISIBILITY + 4:
355 			case GHK_TOGGLE_INVISIBILITY + 5:
356 			case GHK_TOGGLE_INVISIBILITY + 6:
357 			case GHK_TOGGLE_INVISIBILITY + 7:
358 				/* Invisibility toggle hot keys */
359 				ToggleInvisibilityWithTransparency((TransparencyOption)(hotkey - GHK_TOGGLE_INVISIBILITY));
360 				MarkWholeScreenDirty();
361 				break;
362 
363 			case GHK_TRANSPARENCY_TOOLBAR:
364 				ShowTransparencyToolbar();
365 				break;
366 
367 			case GHK_TRANSPARANCY:
368 				ResetRestoreAllTransparency();
369 				break;
370 
371 			case GHK_CHAT: // smart chat; send to team if any, otherwise to all
372 				if (_networking) {
373 					const NetworkClientInfo *cio = NetworkClientInfo::GetByClientID(_network_own_client_id);
374 					if (cio == nullptr) break;
375 
376 					ShowNetworkChatQueryWindow(NetworkClientPreferTeamChat(cio) ? DESTTYPE_TEAM : DESTTYPE_BROADCAST, cio->client_playas);
377 				}
378 				break;
379 
380 			case GHK_CHAT_ALL: // send text message to all clients
381 				if (_networking) ShowNetworkChatQueryWindow(DESTTYPE_BROADCAST, 0);
382 				break;
383 
384 			case GHK_CHAT_COMPANY: // send text to all team mates
385 				if (_networking) {
386 					const NetworkClientInfo *cio = NetworkClientInfo::GetByClientID(_network_own_client_id);
387 					if (cio == nullptr) break;
388 
389 					ShowNetworkChatQueryWindow(DESTTYPE_TEAM, cio->client_playas);
390 				}
391 				break;
392 
393 			case GHK_CHAT_SERVER: // send text to the server
394 				if (_networking && !_network_server) {
395 					ShowNetworkChatQueryWindow(DESTTYPE_CLIENT, CLIENT_ID_SERVER);
396 				}
397 				break;
398 
399 			case GHK_CLOSE_NEWS: // close active news window
400 				if (!HideActiveNewsMessage()) return ES_NOT_HANDLED;
401 				break;
402 
403 			case GHK_CLOSE_ERROR: // close active error window
404 				if (!HideActiveErrorMessage()) return ES_NOT_HANDLED;
405 				break;
406 
407 			default: return ES_NOT_HANDLED;
408 		}
409 		return ES_HANDLED;
410 	}
411 
OnScrollMainWindow412 	void OnScroll(Point delta) override
413 	{
414 		this->viewport->scrollpos_x += ScaleByZoom(delta.x, this->viewport->zoom);
415 		this->viewport->scrollpos_y += ScaleByZoom(delta.y, this->viewport->zoom);
416 		this->viewport->dest_scrollpos_x = this->viewport->scrollpos_x;
417 		this->viewport->dest_scrollpos_y = this->viewport->scrollpos_y;
418 		this->refresh.SetInterval(LINKGRAPH_DELAY);
419 	}
420 
OnMouseWheelMainWindow421 	void OnMouseWheel(int wheel) override
422 	{
423 		if (_settings_client.gui.scrollwheel_scrolling != 2) {
424 			ZoomInOrOutToCursorWindow(wheel < 0, this);
425 		}
426 	}
427 
OnResizeMainWindow428 	void OnResize() override
429 	{
430 		if (this->viewport != nullptr) {
431 			NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_M_VIEWPORT);
432 			nvp->UpdateViewportCoordinates(this);
433 			this->refresh.SetInterval(LINKGRAPH_DELAY);
434 		}
435 	}
436 
437 	/**
438 	 * Some data on this window has become invalid.
439 	 * @param data Information about the changed data.
440 	 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
441 	 */
OnInvalidateDataMainWindow442 	void OnInvalidateData(int data = 0, bool gui_scope = true) override
443 	{
444 		if (!gui_scope) return;
445 		/* Forward the message to the appropriate toolbar (ingame or scenario editor) */
446 		InvalidateWindowData(WC_MAIN_TOOLBAR, 0, data, true);
447 	}
448 
449 	static HotkeyList hotkeys;
450 };
451 
452 const uint16 _ghk_quit_keys[] = {'Q' | WKC_CTRL, 'Q' | WKC_META, 0};
453 const uint16 _ghk_abandon_keys[] = {'W' | WKC_CTRL, 'W' | WKC_META, 0};
454 const uint16 _ghk_chat_keys[] = {WKC_RETURN, 'T', 0};
455 const uint16 _ghk_chat_all_keys[] = {WKC_SHIFT | WKC_RETURN, WKC_SHIFT | 'T', 0};
456 const uint16 _ghk_chat_company_keys[] = {WKC_CTRL | WKC_RETURN, WKC_CTRL | 'T', 0};
457 const uint16 _ghk_chat_server_keys[] = {WKC_CTRL | WKC_SHIFT | WKC_RETURN, WKC_CTRL | WKC_SHIFT | 'T', 0};
458 
459 static Hotkey global_hotkeys[] = {
460 	Hotkey(_ghk_quit_keys, "quit", GHK_QUIT),
461 	Hotkey(_ghk_abandon_keys, "abandon", GHK_ABANDON),
462 	Hotkey(WKC_BACKQUOTE, "console", GHK_CONSOLE),
463 	Hotkey('B' | WKC_CTRL, "bounding_boxes", GHK_BOUNDING_BOXES),
464 	Hotkey('I' | WKC_CTRL, "dirty_blocks", GHK_DIRTY_BLOCKS),
465 	Hotkey('C', "center", GHK_CENTER),
466 	Hotkey('Z', "center_zoom", GHK_CENTER_ZOOM),
467 	Hotkey(WKC_ESC, "reset_object_to_place", GHK_RESET_OBJECT_TO_PLACE),
468 	Hotkey(WKC_DELETE, "delete_windows", GHK_DELETE_WINDOWS),
469 	Hotkey(WKC_DELETE | WKC_SHIFT, "delete_all_windows", GHK_DELETE_NONVITAL_WINDOWS),
470 	Hotkey(WKC_DELETE | WKC_CTRL, "delete_all_messages", GHK_DELETE_ALL_MESSAGES),
471 	Hotkey('R' | WKC_CTRL, "refresh_screen", GHK_REFRESH_SCREEN),
472 #if defined(_DEBUG)
473 	Hotkey('0' | WKC_ALT, "crash_game", GHK_CRASH),
474 	Hotkey('1' | WKC_ALT, "money", GHK_MONEY),
475 	Hotkey('2' | WKC_ALT, "update_coordinates", GHK_UPDATE_COORDS),
476 #endif
477 	Hotkey('1' | WKC_CTRL, "transparency_signs", GHK_TOGGLE_TRANSPARENCY),
478 	Hotkey('2' | WKC_CTRL, "transparency_trees", GHK_TOGGLE_TRANSPARENCY + 1),
479 	Hotkey('3' | WKC_CTRL, "transparency_houses", GHK_TOGGLE_TRANSPARENCY + 2),
480 	Hotkey('4' | WKC_CTRL, "transparency_industries", GHK_TOGGLE_TRANSPARENCY + 3),
481 	Hotkey('5' | WKC_CTRL, "transparency_buildings", GHK_TOGGLE_TRANSPARENCY + 4),
482 	Hotkey('6' | WKC_CTRL, "transparency_bridges", GHK_TOGGLE_TRANSPARENCY + 5),
483 	Hotkey('7' | WKC_CTRL, "transparency_structures", GHK_TOGGLE_TRANSPARENCY + 6),
484 	Hotkey('8' | WKC_CTRL, "transparency_catenary", GHK_TOGGLE_TRANSPARENCY + 7),
485 	Hotkey('9' | WKC_CTRL, "transparency_loading", GHK_TOGGLE_TRANSPARENCY + 8),
486 	Hotkey('1' | WKC_CTRL | WKC_SHIFT, "invisibility_signs", GHK_TOGGLE_INVISIBILITY),
487 	Hotkey('2' | WKC_CTRL | WKC_SHIFT, "invisibility_trees", GHK_TOGGLE_INVISIBILITY + 1),
488 	Hotkey('3' | WKC_CTRL | WKC_SHIFT, "invisibility_houses", GHK_TOGGLE_INVISIBILITY + 2),
489 	Hotkey('4' | WKC_CTRL | WKC_SHIFT, "invisibility_industries", GHK_TOGGLE_INVISIBILITY + 3),
490 	Hotkey('5' | WKC_CTRL | WKC_SHIFT, "invisibility_buildings", GHK_TOGGLE_INVISIBILITY + 4),
491 	Hotkey('6' | WKC_CTRL | WKC_SHIFT, "invisibility_bridges", GHK_TOGGLE_INVISIBILITY + 5),
492 	Hotkey('7' | WKC_CTRL | WKC_SHIFT, "invisibility_structures", GHK_TOGGLE_INVISIBILITY + 6),
493 	Hotkey('8' | WKC_CTRL | WKC_SHIFT, "invisibility_catenary", GHK_TOGGLE_INVISIBILITY + 7),
494 	Hotkey('X' | WKC_CTRL, "transparency_toolbar", GHK_TRANSPARENCY_TOOLBAR),
495 	Hotkey('X', "toggle_transparency", GHK_TRANSPARANCY),
496 	Hotkey(_ghk_chat_keys, "chat", GHK_CHAT),
497 	Hotkey(_ghk_chat_all_keys, "chat_all", GHK_CHAT_ALL),
498 	Hotkey(_ghk_chat_company_keys, "chat_company", GHK_CHAT_COMPANY),
499 	Hotkey(_ghk_chat_server_keys, "chat_server", GHK_CHAT_SERVER),
500 	Hotkey(WKC_SPACE, "close_news", GHK_CLOSE_NEWS),
501 	Hotkey(WKC_SPACE, "close_error", GHK_CLOSE_ERROR),
502 	HOTKEY_LIST_END
503 };
504 HotkeyList MainWindow::hotkeys("global", global_hotkeys);
505 
506 static WindowDesc _main_window_desc(
507 	WDP_MANUAL, nullptr, 0, 0,
508 	WC_MAIN_WINDOW, WC_NONE,
509 	0,
510 	_nested_main_window_widgets, lengthof(_nested_main_window_widgets),
511 	&MainWindow::hotkeys
512 );
513 
514 /**
515  * Does the given keycode match one of the keycodes bound to 'quit game'?
516  * @param keycode The keycode that was pressed by the user.
517  * @return True iff the keycode matches one of the hotkeys for 'quit'.
518  */
IsQuitKey(uint16 keycode)519 bool IsQuitKey(uint16 keycode)
520 {
521 	int num = MainWindow::hotkeys.CheckMatch(keycode);
522 	return num == GHK_QUIT;
523 }
524 
525 
526 void ShowSelectGameWindow();
527 
528 /**
529  * Initialise the default colours (remaps and the likes), and load the main windows.
530  */
SetupColoursAndInitialWindow()531 void SetupColoursAndInitialWindow()
532 {
533 	for (uint i = 0; i != 16; i++) {
534 		const byte *b = GetNonSprite(PALETTE_RECOLOUR_START + i, ST_RECOLOUR);
535 
536 		assert(b);
537 		memcpy(_colour_gradient[i], b + 0xC6, sizeof(_colour_gradient[i]));
538 	}
539 
540 	new MainWindow(&_main_window_desc);
541 
542 	/* XXX: these are not done */
543 	switch (_game_mode) {
544 		default: NOT_REACHED();
545 		case GM_MENU:
546 			ShowSelectGameWindow();
547 			break;
548 
549 		case GM_NORMAL:
550 		case GM_EDITOR:
551 			ShowVitalWindows();
552 			break;
553 	}
554 }
555 
556 /**
557  * Show the vital in-game windows.
558  */
ShowVitalWindows()559 void ShowVitalWindows()
560 {
561 	AllocateToolbar();
562 
563 	/* Status bad only for normal games */
564 	if (_game_mode == GM_EDITOR) return;
565 
566 	ShowStatusBar();
567 }
568 
569 /**
570  * Size of the application screen changed.
571  * Adapt the game screen-size, re-allocate the open windows, and repaint everything
572  */
GameSizeChanged()573 void GameSizeChanged()
574 {
575 	_cur_resolution.width  = _screen.width;
576 	_cur_resolution.height = _screen.height;
577 	ScreenSizeChanged();
578 	RelocateAllWindows(_screen.width, _screen.height);
579 	MarkWholeScreenDirty();
580 }
581