Also scroll tile separators in the train depot
[openttd/fttd.git] / src / error_gui.cpp
blob8bebe5a051177d2910573fcb7313f6ae2d7ccc7c
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 error_gui.cpp GUI related to errors. */
12 #include "stdafx.h"
13 #include "landscape.h"
14 #include "newgrf_text.h"
15 #include "error.h"
16 #include "viewport_func.h"
17 #include "gfx_func.h"
18 #include "string.h"
19 #include "company_base.h"
20 #include "company_manager_face.h"
21 #include "strings_func.h"
22 #include "zoom_func.h"
23 #include "window_func.h"
24 #include "console_func.h"
25 #include "window_gui.h"
27 #include "widgets/error_widget.h"
29 #include "table/strings.h"
30 #include <list>
32 static const NWidgetPart _nested_errmsg_widgets[] = {
33 NWidget(NWID_HORIZONTAL),
34 NWidget(WWT_CLOSEBOX, COLOUR_RED),
35 NWidget(WWT_CAPTION, COLOUR_RED, WID_EM_CAPTION), SetDataTip(STR_ERROR_MESSAGE_CAPTION, STR_NULL),
36 EndContainer(),
37 NWidget(WWT_PANEL, COLOUR_RED),
38 NWidget(WWT_EMPTY, COLOUR_RED, WID_EM_MESSAGE), SetPadding(0, 2, 0, 2), SetMinimalSize(236, 32),
39 EndContainer(),
42 static WindowDesc::Prefs _errmsg_prefs ("error");
44 static const WindowDesc _errmsg_desc(
45 WDP_MANUAL, 0, 0,
46 WC_ERRMSG, WC_NONE,
48 _nested_errmsg_widgets, lengthof(_nested_errmsg_widgets),
49 &_errmsg_prefs
52 static const NWidgetPart _nested_errmsg_face_widgets[] = {
53 NWidget(NWID_HORIZONTAL),
54 NWidget(WWT_CLOSEBOX, COLOUR_RED),
55 NWidget(WWT_CAPTION, COLOUR_RED, WID_EM_CAPTION), SetDataTip(STR_ERROR_MESSAGE_CAPTION_OTHER_COMPANY, STR_NULL),
56 EndContainer(),
57 NWidget(WWT_PANEL, COLOUR_RED),
58 NWidget(NWID_HORIZONTAL), SetPIP(2, 1, 2),
59 NWidget(WWT_EMPTY, COLOUR_RED, WID_EM_FACE), SetMinimalSize(92, 119), SetFill(0, 1), SetPadding(2, 0, 1, 0),
60 NWidget(WWT_EMPTY, COLOUR_RED, WID_EM_MESSAGE), SetFill(0, 1), SetMinimalSize(238, 123),
61 EndContainer(),
62 EndContainer(),
65 static WindowDesc::Prefs _errmsg_face_prefs ("error_face");
67 static const WindowDesc _errmsg_face_desc(
68 WDP_MANUAL, 0, 0,
69 WC_ERRMSG, WC_NONE,
71 _nested_errmsg_face_widgets, lengthof(_nested_errmsg_face_widgets),
72 &_errmsg_face_prefs
75 /**
76 * Copy the given data into our instance.
77 * @param data The data to copy.
79 ErrorMessageData::ErrorMessageData(const ErrorMessageData &data)
81 *this = data;
82 for (size_t i = 0; i < lengthof(this->strings); i++) {
83 if (this->strings[i] != NULL) {
84 this->strings[i] = xstrdup(this->strings[i]);
85 this->decode_params[i] = (size_t)this->strings[i];
90 /** Free all the strings. */
91 ErrorMessageData::~ErrorMessageData()
93 for (size_t i = 0; i < lengthof(this->strings); i++) free(this->strings[i]);
96 /**
97 * Display an error message in a window.
98 * @param summary_msg General error message showed in first line. Must be valid.
99 * @param detailed_msg Detailed error message showed in second line. Can be INVALID_STRING_ID.
100 * @param duration The amount of time to show this error message.
101 * @param x World X position (TileVirtX) of the error location. Set both x and y to 0 to just center the message when there is no related error tile.
102 * @param y World Y position (TileVirtY) of the error location. Set both x and y to 0 to just center the message when there is no related error tile.
103 * @param textref_stack_grffile NewGRF that provides the #TextRefStack for the error message.
104 * @param textref_stack_size Number of uint32 values to put on the #TextRefStack for the error message; 0 if the #TextRefStack shall not be used.
105 * @param textref_stack Values to put on the #TextRefStack.
107 ErrorMessageData::ErrorMessageData(StringID summary_msg, StringID detailed_msg, uint duration, int x, int y, const GRFFile *textref_stack_grffile, uint textref_stack_size, const uint32 *textref_stack) :
108 duration(duration),
109 textref_stack_grffile(textref_stack_grffile),
110 textref_stack_size(textref_stack_size),
111 summary_msg(summary_msg),
112 detailed_msg(detailed_msg),
113 face(INVALID_COMPANY)
115 this->position.x = x;
116 this->position.y = y;
118 memset(this->decode_params, 0, sizeof(this->decode_params));
119 memset(this->strings, 0, sizeof(this->strings));
121 if (textref_stack_size > 0) MemCpyT(this->textref_stack, textref_stack, textref_stack_size);
123 assert(summary_msg != INVALID_STRING_ID);
127 * Copy error parameters from current DParams.
129 void ErrorMessageData::CopyOutDParams()
131 /* Reset parameters */
132 for (size_t i = 0; i < lengthof(this->strings); i++) free(this->strings[i]);
133 memset(this->decode_params, 0, sizeof(this->decode_params));
134 memset(this->strings, 0, sizeof(this->strings));
136 /* Get parameters using type information */
137 if (this->textref_stack_size > 0) StartTextRefStackUsage(this->textref_stack_grffile, this->textref_stack_size, this->textref_stack);
138 CopyOutDParam(this->decode_params, this->strings, this->detailed_msg == INVALID_STRING_ID ? this->summary_msg : this->detailed_msg, lengthof(this->decode_params));
139 if (this->textref_stack_size > 0) StopTextRefStackUsage();
141 if (this->detailed_msg == STR_ERROR_OWNED_BY) {
142 CompanyID company = (CompanyID)this->decode_params[2];
143 if (company < MAX_COMPANIES) face = company;
148 * Set a error string parameter.
149 * @param n Parameter index
150 * @param v Parameter value
152 void ErrorMessageData::SetDParam(uint n, uint64 v)
154 this->decode_params[n] = v;
158 * Set a rawstring parameter.
159 * @param n Parameter index
160 * @param str Raw string
162 void ErrorMessageData::SetDParamStr(uint n, const char *str)
164 free(this->strings[n]);
165 this->strings[n] = xstrdup(str);
168 /** Define a queue with errors. */
169 typedef std::list<ErrorMessageData> ErrorList;
170 /** The actual queue with errors. */
171 ErrorList _error_list;
172 /** Whether the window system is initialized or not. */
173 bool _window_system_initialized = false;
175 /** Window class for displaying an error message window. */
176 struct ErrmsgWindow : public Window, ErrorMessageData {
177 private:
178 uint height_summary; ///< Height of the #summary_msg string in pixels in the #WID_EM_MESSAGE widget.
179 uint height_detailed; ///< Height of the #detailed_msg string in pixels in the #WID_EM_MESSAGE widget.
181 public:
182 ErrmsgWindow(const ErrorMessageData &data)
183 : Window (data.HasFace() ? &_errmsg_face_desc : &_errmsg_desc),
184 ErrorMessageData (data),
185 height_summary (0), height_detailed (0)
187 this->InitNested();
190 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
192 switch (widget) {
193 case WID_EM_MESSAGE: {
194 CopyInDParam(0, this->decode_params, lengthof(this->decode_params));
195 if (this->textref_stack_size > 0) StartTextRefStackUsage(this->textref_stack_grffile, this->textref_stack_size, this->textref_stack);
197 int text_width = max(0, (int)size->width - WD_FRAMETEXT_LEFT - WD_FRAMETEXT_RIGHT);
198 this->height_summary = GetStringHeight(this->summary_msg, text_width);
199 this->height_detailed = (this->detailed_msg == INVALID_STRING_ID) ? 0 : GetStringHeight(this->detailed_msg, text_width);
201 if (this->textref_stack_size > 0) StopTextRefStackUsage();
203 uint panel_height = WD_FRAMERECT_TOP + this->height_summary + WD_FRAMERECT_BOTTOM;
204 if (this->detailed_msg != INVALID_STRING_ID) panel_height += this->height_detailed + WD_PAR_VSEP_WIDE;
206 size->height = max(size->height, panel_height);
207 break;
209 case WID_EM_FACE: {
210 Dimension face_size = GetSpriteSize(SPR_GRADIENT);
211 size->width = max(size->width, face_size.width);
212 size->height = max(size->height, face_size.height);
213 break;
218 virtual Point OnInitialPosition(int16 sm_width, int16 sm_height, int window_number)
220 /* Position (0, 0) given, center the window. */
221 if (this->position.x == 0 && this->position.y == 0) {
222 Point pt = {(_screen_width - sm_width) >> 1, (_screen_height - sm_height) >> 1};
223 return pt;
226 /* Find the free screen space between the main toolbar at the top, and the statusbar at the bottom.
227 * Add a fixed distance 20 to make it less cluttered.
229 int scr_top = GetMainViewTop() + 20;
230 int scr_bot = GetMainViewBottom() - 20;
232 Point pt = RemapCoords2(this->position.x, this->position.y);
233 const ViewPort *vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport;
234 pt.x = UnScaleByZoom (pt.x - vp->virtual_left, vp->zoom) + vp->left;
235 pt.y = UnScaleByZoom (pt.y - vp->virtual_top, vp->zoom) + vp->top;
236 if (this->face == INVALID_COMPANY) {
237 /* move x pos to opposite corner */
238 pt.x = (pt.x < (_screen_width >> 1)) ? _screen_width - sm_width - 20 : 20; // Stay 20 pixels away from the edge of the screen.
240 /* move y pos to opposite corner */
241 pt.y = (pt.y < (_screen_height >> 1)) ? scr_bot - sm_height : scr_top;
242 } else {
243 pt.x = Clamp (pt.x - (sm_width / 2), 0, _screen_width - sm_width);
244 pt.y = Clamp (pt.y - (sm_height / 2), scr_top, scr_bot - sm_height);
246 return pt;
250 * Some data on this window has become invalid.
251 * @param data Information about the changed data.
252 * @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.
254 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
256 /* If company gets shut down, while displaying an error about it, remove the error message. */
257 if (this->face != INVALID_COMPANY && !Company::IsValidID(this->face)) this->Delete();
260 virtual void SetStringParameters(int widget) const
262 if (widget == WID_EM_CAPTION) CopyInDParam(0, this->decode_params, lengthof(this->decode_params));
265 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
267 switch (widget) {
268 case WID_EM_FACE: {
269 const Company *c = Company::Get(this->face);
270 DrawCompanyManagerFace (c->face, c->colour, dpi, r.left, r.top);
271 break;
274 case WID_EM_MESSAGE:
275 CopyInDParam(0, this->decode_params, lengthof(this->decode_params));
276 if (this->textref_stack_size > 0) StartTextRefStackUsage(this->textref_stack_grffile, this->textref_stack_size, this->textref_stack);
278 if (this->detailed_msg == INVALID_STRING_ID) {
279 DrawStringMultiLine (dpi, r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM,
280 this->summary_msg, TC_FROMSTRING, SA_CENTER);
281 } else {
282 int extra = (r.bottom - r.top + 1 - this->height_summary - this->height_detailed - WD_PAR_VSEP_WIDE) / 2;
284 /* Note: NewGRF supplied error message often do not start with a colour code, so default to white. */
285 int top = r.top + WD_FRAMERECT_TOP;
286 int bottom = top + this->height_summary + extra;
287 DrawStringMultiLine (dpi, r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, top, bottom, this->summary_msg, TC_WHITE, SA_CENTER);
289 bottom = r.bottom - WD_FRAMERECT_BOTTOM;
290 top = bottom - this->height_detailed - extra;
291 DrawStringMultiLine (dpi, r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, top, bottom, this->detailed_msg, TC_WHITE, SA_CENTER);
294 if (this->textref_stack_size > 0) StopTextRefStackUsage();
295 break;
297 default:
298 break;
302 virtual void OnMouseLoop()
304 /* Disallow closing the window too easily, if timeout is disabled */
305 if (_right_button_down && this->duration != 0) this->Delete();
308 virtual void OnHundredthTick()
310 /* Timeout enabled? */
311 if (this->duration != 0) {
312 this->duration--;
313 if (this->duration == 0) this->Delete();
317 void OnDelete (void) FINAL_OVERRIDE
319 SetRedErrorSquare(INVALID_TILE);
320 if (_window_system_initialized) ShowFirstError();
323 bool OnKeyPress (WChar key, uint16 keycode) OVERRIDE
325 if (keycode != WKC_SPACE) return false;
326 this->Delete();
327 return true;
331 * Check whether the currently shown error message was critical or not.
332 * @return True iff the message was critical.
334 bool IsCritical()
336 return this->duration == 0;
341 * Clear all errors from the queue.
343 void ClearErrorMessages()
345 UnshowCriticalError();
346 _error_list.clear();
349 /** Show the first error of the queue. */
350 void ShowFirstError()
352 _window_system_initialized = true;
353 if (!_error_list.empty()) {
354 new ErrmsgWindow(_error_list.front());
355 _error_list.pop_front();
360 * Unshow the critical error. This has to happen when a critical
361 * error is shown and we uninitialise the window system, i.e.
362 * remove all the windows.
364 void UnshowCriticalError()
366 ErrmsgWindow *w = (ErrmsgWindow*)FindWindowById(WC_ERRMSG, 0);
367 if (_window_system_initialized && w != NULL) {
368 if (w->IsCritical()) _error_list.push_front(*w);
369 _window_system_initialized = false;
370 w->Delete();
375 * Display an error message in a window.
376 * @param summary_msg General error message showed in first line. Must be valid.
377 * @param detailed_msg Detailed error message showed in second line. Can be INVALID_STRING_ID.
378 * @param wl Message severity.
379 * @param x World X position (TileVirtX) of the error location. Set both x and y to 0 to just center the message when there is no related error tile.
380 * @param y World Y position (TileVirtY) of the error location. Set both x and y to 0 to just center the message when there is no related error tile.
381 * @param textref_stack_grffile NewGRF providing the #TextRefStack for the error message.
382 * @param textref_stack_size Number of uint32 values to put on the #TextRefStack for the error message; 0 if the #TextRefStack shall not be used.
383 * @param textref_stack Values to put on the #TextRefStack.
385 void ShowErrorMessage(StringID summary_msg, StringID detailed_msg, WarningLevel wl, int x, int y, const GRFFile *textref_stack_grffile, uint textref_stack_size, const uint32 *textref_stack)
387 assert(textref_stack_size == 0 || (textref_stack_grffile != NULL && textref_stack != NULL));
388 if (summary_msg == STR_NULL) summary_msg = STR_EMPTY;
390 if (wl != WL_INFO) {
391 /* Print message to console */
392 sstring<DRAW_STRING_BUFFER> buf;
394 if (textref_stack_size > 0) StartTextRefStackUsage(textref_stack_grffile, textref_stack_size, textref_stack);
396 GetString (&buf, summary_msg);
397 if (detailed_msg != INVALID_STRING_ID) {
398 buf.append (' ');
399 AppendString (&buf, detailed_msg);
402 if (textref_stack_size > 0) StopTextRefStackUsage();
404 switch (wl) {
405 case WL_WARNING: IConsolePrint(CC_WARNING, buf.c_str()); break;
406 default: IConsoleError(buf.c_str()); break;
410 bool no_timeout = wl == WL_CRITICAL;
412 if (_settings_client.gui.errmsg_duration == 0 && !no_timeout) return;
414 ErrorMessageData data(summary_msg, detailed_msg, no_timeout ? 0 : _settings_client.gui.errmsg_duration, x, y, textref_stack_grffile, textref_stack_size, textref_stack);
415 data.CopyOutDParams();
417 ErrmsgWindow *w = (ErrmsgWindow*)FindWindowById(WC_ERRMSG, 0);
418 if (w != NULL && w->IsCritical()) {
419 /* A critical error is currently shown. */
420 if (wl == WL_CRITICAL) {
421 /* Push another critical error in the queue of errors,
422 * but do not put other errors in the queue. */
423 _error_list.push_back(data);
425 } else {
426 /* Nothing or a non-critical error was shown. */
427 if (w != NULL) w->Delete();
428 new ErrmsgWindow(data);
433 * Schedule a list of errors.
434 * Note: This does not try to display the error now. This is useful if the window system is not yet running.
435 * @param data Error message datas; cleared afterwards
437 void ScheduleErrorMessage(ErrorList &datas)
439 _error_list.splice(_error_list.end(), datas);
443 * Schedule an error.
444 * Note: This does not try to display the error now. This is useful if the window system is not yet running.
445 * @param data Error message data; cleared afterwards
447 void ScheduleErrorMessage(const ErrorMessageData &data)
449 _error_list.push_back(data);