Add: Towns can build tunnels (#8473)
[openttd-github.git] / src / news_gui.cpp
blob639c0757461e961e3ec9314c862e85e5666baf18
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 */
8 /** @file news_gui.cpp GUI functions related to news messages. */
10 #include "stdafx.h"
11 #include "gui.h"
12 #include "viewport_func.h"
13 #include "strings_func.h"
14 #include "window_func.h"
15 #include "date_func.h"
16 #include "vehicle_base.h"
17 #include "vehicle_func.h"
18 #include "vehicle_gui.h"
19 #include "roadveh.h"
20 #include "station_base.h"
21 #include "industry.h"
22 #include "town.h"
23 #include "sound_func.h"
24 #include "string_func.h"
25 #include "widgets/dropdown_func.h"
26 #include "statusbar_gui.h"
27 #include "company_manager_face.h"
28 #include "company_func.h"
29 #include "engine_base.h"
30 #include "engine_gui.h"
31 #include "core/geometry_func.hpp"
32 #include "command_func.h"
33 #include "company_base.h"
34 #include "settings_internal.h"
35 #include "guitimer_func.h"
36 #include "group_gui.h"
38 #include "widgets/news_widget.h"
40 #include "table/strings.h"
42 #include "safeguards.h"
44 const NewsItem *_statusbar_news_item = nullptr;
46 static uint MIN_NEWS_AMOUNT = 30; ///< preferred minimum amount of news messages
47 static uint MAX_NEWS_AMOUNT = 1 << 10; ///< Do not exceed this number of news messages
48 static uint _total_news = 0; ///< current number of news items
49 static NewsItem *_oldest_news = nullptr; ///< head of news items queue
50 NewsItem *_latest_news = nullptr; ///< tail of news items queue
52 /**
53 * Forced news item.
54 * Users can force an item by accessing the history or "last message".
55 * If the message being shown was forced by the user, a pointer is stored
56 * in _forced_news. Otherwise, \a _forced_news variable is nullptr.
58 static const NewsItem *_forced_news = nullptr;
60 /** Current news item (last item shown regularly). */
61 static const NewsItem *_current_news = nullptr;
64 /**
65 * Get the position a news-reference is referencing.
66 * @param reftype The type of reference.
67 * @param ref The reference.
68 * @return A tile for the referenced object, or INVALID_TILE if none.
70 static TileIndex GetReferenceTile(NewsReferenceType reftype, uint32 ref)
72 switch (reftype) {
73 case NR_TILE: return (TileIndex)ref;
74 case NR_STATION: return Station::Get((StationID)ref)->xy;
75 case NR_INDUSTRY: return Industry::Get((IndustryID)ref)->location.tile + TileDiffXY(1, 1);
76 case NR_TOWN: return Town::Get((TownID)ref)->xy;
77 default: return INVALID_TILE;
81 /* Normal news items. */
82 static const NWidgetPart _nested_normal_news_widgets[] = {
83 NWidget(WWT_PANEL, COLOUR_WHITE, WID_N_PANEL),
84 NWidget(NWID_HORIZONTAL), SetPadding(1, 1, 0, 1),
85 NWidget(WWT_CLOSEBOX, COLOUR_WHITE, WID_N_CLOSEBOX), SetPadding(0, 0, 0, 1),
86 NWidget(NWID_SPACER), SetFill(1, 0),
87 NWidget(NWID_VERTICAL),
88 NWidget(WWT_LABEL, COLOUR_WHITE, WID_N_DATE), SetDataTip(STR_DATE_LONG_SMALL, STR_NULL),
89 NWidget(NWID_SPACER), SetFill(0, 1),
90 EndContainer(),
91 EndContainer(),
92 NWidget(WWT_EMPTY, COLOUR_WHITE, WID_N_MESSAGE), SetMinimalSize(428, 154), SetPadding(0, 5, 1, 5),
93 EndContainer(),
96 static WindowDesc _normal_news_desc(
97 WDP_MANUAL, nullptr, 0, 0,
98 WC_NEWS_WINDOW, WC_NONE,
100 _nested_normal_news_widgets, lengthof(_nested_normal_news_widgets)
103 /* New vehicles news items. */
104 static const NWidgetPart _nested_vehicle_news_widgets[] = {
105 NWidget(WWT_PANEL, COLOUR_WHITE, WID_N_PANEL),
106 NWidget(NWID_HORIZONTAL), SetPadding(1, 1, 0, 1),
107 NWidget(NWID_VERTICAL),
108 NWidget(WWT_CLOSEBOX, COLOUR_WHITE, WID_N_CLOSEBOX), SetPadding(0, 0, 0, 1),
109 NWidget(NWID_SPACER), SetFill(0, 1),
110 EndContainer(),
111 NWidget(WWT_LABEL, COLOUR_WHITE, WID_N_VEH_TITLE), SetFill(1, 1), SetMinimalSize(419, 55), SetDataTip(STR_EMPTY, STR_NULL),
112 EndContainer(),
113 NWidget(WWT_PANEL, COLOUR_WHITE, WID_N_VEH_BKGND), SetPadding(0, 25, 1, 25),
114 NWidget(NWID_VERTICAL),
115 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_N_VEH_NAME), SetMinimalSize(369, 33), SetFill(1, 0),
116 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_N_VEH_SPR), SetMinimalSize(369, 32), SetFill(1, 0),
117 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_N_VEH_INFO), SetMinimalSize(369, 46), SetFill(1, 0),
118 EndContainer(),
119 EndContainer(),
120 EndContainer(),
123 static WindowDesc _vehicle_news_desc(
124 WDP_MANUAL, nullptr, 0, 0,
125 WC_NEWS_WINDOW, WC_NONE,
127 _nested_vehicle_news_widgets, lengthof(_nested_vehicle_news_widgets)
130 /* Company news items. */
131 static const NWidgetPart _nested_company_news_widgets[] = {
132 NWidget(WWT_PANEL, COLOUR_WHITE, WID_N_PANEL),
133 NWidget(NWID_HORIZONTAL), SetPadding(1, 1, 0, 1),
134 NWidget(NWID_VERTICAL),
135 NWidget(WWT_CLOSEBOX, COLOUR_WHITE, WID_N_CLOSEBOX), SetPadding(0, 0, 0, 1),
136 NWidget(NWID_SPACER), SetFill(0, 1),
137 EndContainer(),
138 NWidget(WWT_LABEL, COLOUR_WHITE, WID_N_TITLE), SetFill(1, 1), SetMinimalSize(410, 20), SetDataTip(STR_EMPTY, STR_NULL),
139 EndContainer(),
140 NWidget(NWID_HORIZONTAL), SetPadding(0, 1, 1, 1),
141 NWidget(NWID_VERTICAL),
142 NWidget(WWT_EMPTY, COLOUR_WHITE, WID_N_MGR_FACE), SetMinimalSize(93, 119), SetPadding(2, 6, 2, 1),
143 NWidget(WWT_EMPTY, COLOUR_WHITE, WID_N_MGR_NAME), SetMinimalSize(93, 24), SetPadding(0, 0, 0, 1),
144 NWidget(NWID_SPACER), SetFill(0, 1),
145 EndContainer(),
146 NWidget(WWT_EMPTY, COLOUR_WHITE, WID_N_COMPANY_MSG), SetFill(1, 1), SetMinimalSize(328, 150),
147 EndContainer(),
148 EndContainer(),
151 static WindowDesc _company_news_desc(
152 WDP_MANUAL, nullptr, 0, 0,
153 WC_NEWS_WINDOW, WC_NONE,
155 _nested_company_news_widgets, lengthof(_nested_company_news_widgets)
158 /* Thin news items. */
159 static const NWidgetPart _nested_thin_news_widgets[] = {
160 NWidget(WWT_PANEL, COLOUR_WHITE, WID_N_PANEL),
161 NWidget(NWID_HORIZONTAL), SetPadding(1, 1, 0, 1),
162 NWidget(WWT_CLOSEBOX, COLOUR_WHITE, WID_N_CLOSEBOX), SetPadding(0, 0, 0, 1),
163 NWidget(NWID_SPACER), SetFill(1, 0),
164 NWidget(NWID_VERTICAL),
165 NWidget(WWT_LABEL, COLOUR_WHITE, WID_N_DATE), SetDataTip(STR_DATE_LONG_SMALL, STR_NULL),
166 NWidget(NWID_SPACER), SetFill(0, 1),
167 EndContainer(),
168 EndContainer(),
169 NWidget(WWT_EMPTY, COLOUR_WHITE, WID_N_MESSAGE), SetMinimalSize(428, 48), SetFill(1, 0), SetPadding(0, 5, 0, 5),
170 NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_N_VIEWPORT), SetMinimalSize(426, 70), SetPadding(1, 2, 2, 2),
171 EndContainer(),
174 static WindowDesc _thin_news_desc(
175 WDP_MANUAL, nullptr, 0, 0,
176 WC_NEWS_WINDOW, WC_NONE,
178 _nested_thin_news_widgets, lengthof(_nested_thin_news_widgets)
181 /* Small news items. */
182 static const NWidgetPart _nested_small_news_widgets[] = {
183 /* Caption + close box. The caption is no WWT_CAPTION as the window shall not be moveable and so on. */
184 NWidget(NWID_HORIZONTAL),
185 NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE, WID_N_CLOSEBOX),
186 NWidget(WWT_EMPTY, COLOUR_LIGHT_BLUE, WID_N_CAPTION), SetFill(1, 0),
187 NWidget(WWT_TEXTBTN, COLOUR_LIGHT_BLUE, WID_N_SHOW_GROUP), SetMinimalSize(14, 11), SetResize(1, 0),
188 SetDataTip(STR_NULL /* filled in later */, STR_NEWS_SHOW_VEHICLE_GROUP_TOOLTIP),
189 EndContainer(),
191 /* Main part */
192 NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_N_HEADLINE),
193 NWidget(WWT_INSET, COLOUR_LIGHT_BLUE, WID_N_INSET), SetPadding(2, 2, 2, 2),
194 NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_N_VIEWPORT), SetPadding(1, 1, 1, 1), SetMinimalSize(274, 47), SetFill(1, 0),
195 EndContainer(),
196 NWidget(WWT_EMPTY, COLOUR_WHITE, WID_N_MESSAGE), SetMinimalSize(275, 20), SetFill(1, 0), SetPadding(0, 5, 0, 5),
197 EndContainer(),
200 static WindowDesc _small_news_desc(
201 WDP_MANUAL, nullptr, 0, 0,
202 WC_NEWS_WINDOW, WC_NONE,
204 _nested_small_news_widgets, lengthof(_nested_small_news_widgets)
208 * Window layouts for news items.
210 static WindowDesc* _news_window_layout[] = {
211 &_thin_news_desc, ///< NF_THIN
212 &_small_news_desc, ///< NF_SMALL
213 &_normal_news_desc, ///< NF_NORMAL
214 &_vehicle_news_desc, ///< NF_VEHICLE
215 &_company_news_desc, ///< NF_COMPANY
218 WindowDesc* GetNewsWindowLayout(NewsFlag flags)
220 uint layout = GB(flags, NFB_WINDOW_LAYOUT, NFB_WINDOW_LAYOUT_COUNT);
221 assert(layout < lengthof(_news_window_layout));
222 return _news_window_layout[layout];
226 * Per-NewsType data
228 static NewsTypeData _news_type_data[] = {
229 /* name, age, sound, */
230 NewsTypeData("news_display.arrival_player", 60, SND_1D_APPLAUSE ), ///< NT_ARRIVAL_COMPANY
231 NewsTypeData("news_display.arrival_other", 60, SND_1D_APPLAUSE ), ///< NT_ARRIVAL_OTHER
232 NewsTypeData("news_display.accident", 90, SND_BEGIN ), ///< NT_ACCIDENT
233 NewsTypeData("news_display.company_info", 60, SND_BEGIN ), ///< NT_COMPANY_INFO
234 NewsTypeData("news_display.open", 90, SND_BEGIN ), ///< NT_INDUSTRY_OPEN
235 NewsTypeData("news_display.close", 90, SND_BEGIN ), ///< NT_INDUSTRY_CLOSE
236 NewsTypeData("news_display.economy", 30, SND_BEGIN ), ///< NT_ECONOMY
237 NewsTypeData("news_display.production_player", 30, SND_BEGIN ), ///< NT_INDUSTRY_COMPANY
238 NewsTypeData("news_display.production_other", 30, SND_BEGIN ), ///< NT_INDUSTRY_OTHER
239 NewsTypeData("news_display.production_nobody", 30, SND_BEGIN ), ///< NT_INDUSTRY_NOBODY
240 NewsTypeData("news_display.advice", 150, SND_BEGIN ), ///< NT_ADVICE
241 NewsTypeData("news_display.new_vehicles", 30, SND_1E_OOOOH ), ///< NT_NEW_VEHICLES
242 NewsTypeData("news_display.acceptance", 90, SND_BEGIN ), ///< NT_ACCEPTANCE
243 NewsTypeData("news_display.subsidies", 180, SND_BEGIN ), ///< NT_SUBSIDIES
244 NewsTypeData("news_display.general", 60, SND_BEGIN ), ///< NT_GENERAL
247 static_assert(lengthof(_news_type_data) == NT_END);
250 * Return the news display option.
251 * @return display options
253 NewsDisplay NewsTypeData::GetDisplay() const
255 uint index;
256 const SettingDesc *sd = GetSettingFromName(this->name, &index);
257 assert(sd != nullptr);
258 void *ptr = GetVariableAddress(nullptr, &sd->save);
259 return (NewsDisplay)ReadValue(ptr, sd->save.conv);
262 /** Window class displaying a news item. */
263 struct NewsWindow : Window {
264 uint16 chat_height; ///< Height of the chat window.
265 uint16 status_height; ///< Height of the status bar window
266 const NewsItem *ni; ///< News item to display.
267 static int duration; ///< Remaining time for showing the current news message (may only be access while a news item is displayed).
269 GUITimer timer;
271 NewsWindow(WindowDesc *desc, const NewsItem *ni) : Window(desc), ni(ni)
273 NewsWindow::duration = 16650;
274 const Window *w = FindWindowByClass(WC_SEND_NETWORK_MSG);
275 this->chat_height = (w != nullptr) ? w->height : 0;
276 this->status_height = FindWindowById(WC_STATUS_BAR, 0)->height;
278 this->flags |= WF_DISABLE_VP_SCROLL;
280 this->timer.SetInterval(15);
282 this->CreateNestedTree();
284 /* For company news with a face we have a separate headline in param[0] */
285 if (desc == &_company_news_desc) this->GetWidget<NWidgetCore>(WID_N_TITLE)->widget_data = this->ni->params[0];
287 NWidgetCore *nwid = this->GetWidget<NWidgetCore>(WID_N_SHOW_GROUP);
288 if (ni->reftype1 == NR_VEHICLE && nwid != nullptr) {
289 const Vehicle *v = Vehicle::Get(ni->ref1);
290 switch (v->type) {
291 case VEH_TRAIN:
292 nwid->widget_data = STR_TRAIN;
293 break;
294 case VEH_ROAD:
295 nwid->widget_data = RoadVehicle::From(v)->IsBus() ? STR_BUS : STR_LORRY;
296 break;
297 case VEH_SHIP:
298 nwid->widget_data = STR_SHIP;
299 break;
300 case VEH_AIRCRAFT:
301 nwid->widget_data = STR_PLANE;
302 break;
303 default:
304 break; // Do nothing
308 this->FinishInitNested(0);
310 /* Initialize viewport if it exists. */
311 NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_N_VIEWPORT);
312 if (nvp != nullptr) {
313 nvp->InitializeViewport(this, ni->reftype1 == NR_VEHICLE ? 0x80000000 | ni->ref1 : GetReferenceTile(ni->reftype1, ni->ref1), ZOOM_LVL_NEWS);
314 if (this->ni->flags & NF_NO_TRANSPARENT) nvp->disp_flags |= ND_NO_TRANSPARENCY;
315 if ((this->ni->flags & NF_INCOLOUR) == 0) {
316 nvp->disp_flags |= ND_SHADE_GREY;
317 } else if (this->ni->flags & NF_SHADE) {
318 nvp->disp_flags |= ND_SHADE_DIMMED;
322 PositionNewsMessage(this);
325 void DrawNewsBorder(const Rect &r) const
327 GfxFillRect(r.left, r.top, r.right, r.bottom, PC_WHITE);
329 GfxFillRect(r.left, r.top, r.left, r.bottom, PC_BLACK);
330 GfxFillRect(r.right, r.top, r.right, r.bottom, PC_BLACK);
331 GfxFillRect(r.left, r.top, r.right, r.top, PC_BLACK);
332 GfxFillRect(r.left, r.bottom, r.right, r.bottom, PC_BLACK);
335 Point OnInitialPosition(int16 sm_width, int16 sm_height, int window_number) override
337 Point pt = { 0, _screen.height };
338 return pt;
341 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
343 StringID str = STR_NULL;
344 switch (widget) {
345 case WID_N_CAPTION: {
346 /* Caption is not a real caption (so that the window cannot be moved)
347 * thus it doesn't get the default sizing of a caption. */
348 Dimension d2 = GetStringBoundingBox(STR_NEWS_MESSAGE_CAPTION);
349 d2.height += WD_CAPTIONTEXT_TOP + WD_CAPTIONTEXT_BOTTOM;
350 *size = maxdim(*size, d2);
351 return;
354 case WID_N_MGR_FACE:
355 *size = maxdim(*size, GetSpriteSize(SPR_GRADIENT));
356 break;
358 case WID_N_MGR_NAME:
359 SetDParamStr(0, static_cast<const CompanyNewsInformation *>(this->ni->free_data)->president_name);
360 str = STR_JUST_RAW_STRING;
361 break;
363 case WID_N_MESSAGE:
364 CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
365 str = this->ni->string_id;
366 break;
368 case WID_N_COMPANY_MSG:
369 str = this->GetCompanyMessageString();
370 break;
372 case WID_N_VEH_NAME:
373 case WID_N_VEH_TITLE:
374 str = this->GetNewVehicleMessageString(widget);
375 break;
377 case WID_N_VEH_INFO: {
378 assert(this->ni->reftype1 == NR_ENGINE);
379 EngineID engine = this->ni->ref1;
380 str = GetEngineInfoString(engine);
381 break;
384 case WID_N_SHOW_GROUP:
385 if (this->ni->reftype1 == NR_VEHICLE) {
386 Dimension d2 = GetStringBoundingBox(this->GetWidget<NWidgetCore>(WID_N_SHOW_GROUP)->widget_data);
387 d2.height += WD_CAPTIONTEXT_TOP + WD_CAPTIONTEXT_BOTTOM;
388 d2.width += WD_CAPTIONTEXT_LEFT + WD_CAPTIONTEXT_RIGHT;
389 *size = d2;
390 } else {
391 /* Hide 'Show group window' button if this news is not about a vehicle. */
392 size->width = 0;
393 size->height = 0;
394 resize->width = 0;
395 resize->height = 0;
396 fill->width = 0;
397 fill->height = 0;
399 return;
401 default:
402 return; // Do nothing
405 /* Update minimal size with length of the multi-line string. */
406 Dimension d = *size;
407 d.width = (d.width >= padding.width) ? d.width - padding.width : 0;
408 d.height = (d.height >= padding.height) ? d.height - padding.height : 0;
409 d = GetStringMultiLineBoundingBox(str, d);
410 d.width += padding.width;
411 d.height += padding.height;
412 *size = maxdim(*size, d);
415 void SetStringParameters(int widget) const override
417 if (widget == WID_N_DATE) SetDParam(0, this->ni->date);
420 void DrawWidget(const Rect &r, int widget) const override
422 switch (widget) {
423 case WID_N_CAPTION:
424 DrawCaption(r, COLOUR_LIGHT_BLUE, this->owner, STR_NEWS_MESSAGE_CAPTION);
425 break;
427 case WID_N_PANEL:
428 this->DrawNewsBorder(r);
429 break;
431 case WID_N_MESSAGE:
432 CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
433 DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->ni->string_id, TC_FROMSTRING, SA_CENTER);
434 break;
436 case WID_N_MGR_FACE: {
437 const CompanyNewsInformation *cni = (const CompanyNewsInformation*)this->ni->free_data;
438 DrawCompanyManagerFace(cni->face, cni->colour, r.left, r.top);
439 GfxFillRect(r.left, r.top, r.right, r.bottom, PALETTE_NEWSPAPER, FILLRECT_RECOLOUR);
440 break;
442 case WID_N_MGR_NAME: {
443 const CompanyNewsInformation *cni = (const CompanyNewsInformation*)this->ni->free_data;
444 SetDParamStr(0, cni->president_name);
445 DrawStringMultiLine(r.left, r.right, r.top, r.bottom, STR_JUST_RAW_STRING, TC_FROMSTRING, SA_CENTER);
446 break;
448 case WID_N_COMPANY_MSG:
449 DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->GetCompanyMessageString(), TC_FROMSTRING, SA_CENTER);
450 break;
452 case WID_N_VEH_BKGND:
453 GfxFillRect(r.left, r.top, r.right, r.bottom, PC_GREY);
454 break;
456 case WID_N_VEH_NAME:
457 case WID_N_VEH_TITLE:
458 DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->GetNewVehicleMessageString(widget), TC_FROMSTRING, SA_CENTER);
459 break;
461 case WID_N_VEH_SPR: {
462 assert(this->ni->reftype1 == NR_ENGINE);
463 EngineID engine = this->ni->ref1;
464 DrawVehicleEngine(r.left, r.right, (r.left + r.right) / 2, (r.top + r.bottom) / 2, engine, GetEnginePalette(engine, _local_company), EIT_PREVIEW);
465 GfxFillRect(r.left, r.top, r.right, r.bottom, PALETTE_NEWSPAPER, FILLRECT_RECOLOUR);
466 break;
468 case WID_N_VEH_INFO: {
469 assert(this->ni->reftype1 == NR_ENGINE);
470 EngineID engine = this->ni->ref1;
471 DrawStringMultiLine(r.left, r.right, r.top, r.bottom, GetEngineInfoString(engine), TC_FROMSTRING, SA_CENTER);
472 break;
477 void OnClick(Point pt, int widget, int click_count) override
479 switch (widget) {
480 case WID_N_CLOSEBOX:
481 NewsWindow::duration = 0;
482 delete this;
483 _forced_news = nullptr;
484 break;
486 case WID_N_CAPTION:
487 if (this->ni->reftype1 == NR_VEHICLE) {
488 const Vehicle *v = Vehicle::Get(this->ni->ref1);
489 ShowVehicleViewWindow(v);
491 break;
493 case WID_N_VIEWPORT:
494 break; // Ignore clicks
496 case WID_N_SHOW_GROUP:
497 if (this->ni->reftype1 == NR_VEHICLE) {
498 const Vehicle *v = Vehicle::Get(this->ni->ref1);
499 ShowCompanyGroupForVehicle(v);
501 break;
502 default:
503 if (this->ni->reftype1 == NR_VEHICLE) {
504 const Vehicle *v = Vehicle::Get(this->ni->ref1);
505 ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos);
506 } else {
507 TileIndex tile1 = GetReferenceTile(this->ni->reftype1, this->ni->ref1);
508 TileIndex tile2 = GetReferenceTile(this->ni->reftype2, this->ni->ref2);
509 if (_ctrl_pressed) {
510 if (tile1 != INVALID_TILE) ShowExtraViewportWindow(tile1);
511 if (tile2 != INVALID_TILE) ShowExtraViewportWindow(tile2);
512 } else {
513 if ((tile1 == INVALID_TILE || !ScrollMainWindowToTile(tile1)) && tile2 != INVALID_TILE) {
514 ScrollMainWindowToTile(tile2);
518 break;
523 * Some data on this window has become invalid.
524 * @param data Information about the changed data.
525 * @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.
527 void OnInvalidateData(int data = 0, bool gui_scope = true) override
529 if (!gui_scope) return;
530 /* The chatbar has notified us that is was either created or closed */
531 int newtop = this->top + this->chat_height - data;
532 this->chat_height = data;
533 this->SetWindowTop(newtop);
536 void OnRealtimeTick(uint delta_ms) override
538 int count = this->timer.CountElapsed(delta_ms);
539 if (count > 0) {
540 /* Scroll up newsmessages from the bottom */
541 int newtop = std::max(this->top - 2 * count, _screen.height - this->height - this->status_height - this->chat_height);
542 this->SetWindowTop(newtop);
545 /* Decrement the news timer. We don't need to action an elapsed event here,
546 * so no need to use TimerElapsed(). */
547 if (NewsWindow::duration > 0) NewsWindow::duration -= delta_ms;
550 private:
552 * Moves the window to a new #top coordinate. Makes screen dirty where needed.
553 * @param newtop new top coordinate
555 void SetWindowTop(int newtop)
557 if (this->top == newtop) return;
559 int mintop = std::min(newtop, this->top);
560 int maxtop = std::max(newtop, this->top);
561 if (this->viewport != nullptr) this->viewport->top += newtop - this->top;
562 this->top = newtop;
564 AddDirtyBlock(this->left, mintop, this->left + this->width, maxtop + this->height);
567 StringID GetCompanyMessageString() const
569 /* Company news with a face have a separate headline, so the normal message is shifted by two params */
570 CopyInDParam(0, this->ni->params + 2, lengthof(this->ni->params) - 2);
571 return this->ni->params[1];
574 StringID GetNewVehicleMessageString(int widget) const
576 assert(this->ni->reftype1 == NR_ENGINE);
577 EngineID engine = this->ni->ref1;
579 switch (widget) {
580 case WID_N_VEH_TITLE:
581 SetDParam(0, GetEngineCategoryName(engine));
582 return STR_NEWS_NEW_VEHICLE_NOW_AVAILABLE;
584 case WID_N_VEH_NAME:
585 SetDParam(0, engine);
586 return STR_NEWS_NEW_VEHICLE_TYPE;
588 default:
589 NOT_REACHED();
594 /* static */ int NewsWindow::duration = 0; // Instance creation.
596 /** Open up an own newspaper window for the news item */
597 static void ShowNewspaper(const NewsItem *ni)
599 SoundFx sound = _news_type_data[ni->type].sound;
600 if (sound != 0 && _settings_client.sound.news_full) SndPlayFx(sound);
602 new NewsWindow(GetNewsWindowLayout(ni->flags), ni);
605 /** Show news item in the ticker */
606 static void ShowTicker(const NewsItem *ni)
608 if (_settings_client.sound.news_ticker) SndPlayFx(SND_16_MORSE);
610 _statusbar_news_item = ni;
611 InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_TICKER);
614 /** Initialize the news-items data structures */
615 void InitNewsItemStructs()
617 for (NewsItem *ni = _oldest_news; ni != nullptr; ) {
618 NewsItem *next = ni->next;
619 delete ni;
620 ni = next;
623 _total_news = 0;
624 _oldest_news = nullptr;
625 _latest_news = nullptr;
626 _forced_news = nullptr;
627 _current_news = nullptr;
628 _statusbar_news_item = nullptr;
629 NewsWindow::duration = 0;
633 * Are we ready to show another ticker item?
634 * Only if nothing is in the newsticker is displayed
636 static bool ReadyForNextTickerItem()
638 const NewsItem *ni = _statusbar_news_item;
639 if (ni == nullptr) return true;
641 /* Ticker message
642 * Check if the status bar message is still being displayed? */
643 if (IsNewsTickerShown()) return false;
644 return true;
648 * Are we ready to show another news item?
649 * Only if no newspaper is displayed
651 static bool ReadyForNextNewsItem()
653 const NewsItem *ni = _forced_news == nullptr ? _current_news : _forced_news;
654 if (ni == nullptr) return true;
656 /* neither newsticker nor newspaper are running */
657 return (NewsWindow::duration <= 0 || FindWindowById(WC_NEWS_WINDOW, 0) == nullptr);
660 /** Move to the next ticker item */
661 static void MoveToNextTickerItem()
663 /* There is no status bar, so no reason to show news;
664 * especially important with the end game screen when
665 * there is no status bar but possible news. */
666 if (FindWindowById(WC_STATUS_BAR, 0) == nullptr) return;
668 InvalidateWindowData(WC_STATUS_BAR, 0, SBI_NEWS_DELETED); // invalidate the statusbar
670 /* if we're not at the last item, then move on */
671 while (_statusbar_news_item != _latest_news) {
672 _statusbar_news_item = (_statusbar_news_item == nullptr) ? _oldest_news : _statusbar_news_item->next;
673 const NewsItem *ni = _statusbar_news_item;
674 const NewsType type = ni->type;
676 /* check the date, don't show too old items */
677 if (_date - _news_type_data[type].age > ni->date) continue;
679 switch (_news_type_data[type].GetDisplay()) {
680 default: NOT_REACHED();
681 case ND_OFF: // Off - show nothing only a small reminder in the status bar
682 InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_REMINDER);
683 break;
685 case ND_SUMMARY: // Summary - show ticker
686 ShowTicker(ni);
687 break;
689 case ND_FULL: // Full - show newspaper, skipped here
690 continue;
692 return;
696 /** Move to the next news item */
697 static void MoveToNextNewsItem()
699 /* There is no status bar, so no reason to show news;
700 * especially important with the end game screen when
701 * there is no status bar but possible news. */
702 if (FindWindowById(WC_STATUS_BAR, 0) == nullptr) return;
704 DeleteWindowById(WC_NEWS_WINDOW, 0); // close the newspapers window if shown
705 _forced_news = nullptr;
707 /* if we're not at the last item, then move on */
708 while (_current_news != _latest_news) {
709 _current_news = (_current_news == nullptr) ? _oldest_news : _current_news->next;
710 const NewsItem *ni = _current_news;
711 const NewsType type = ni->type;
713 /* check the date, don't show too old items */
714 if (_date - _news_type_data[type].age > ni->date) continue;
716 switch (_news_type_data[type].GetDisplay()) {
717 default: NOT_REACHED();
718 case ND_OFF: // Off - show nothing only a small reminder in the status bar, skipped here
719 continue;
721 case ND_SUMMARY: // Summary - show ticker, skipped here
722 continue;
724 case ND_FULL: // Full - show newspaper
725 ShowNewspaper(ni);
726 break;
728 return;
732 /** Delete a news item from the queue */
733 static void DeleteNewsItem(NewsItem *ni)
735 /* Delete the news from the news queue. */
736 if (ni->prev != nullptr) {
737 ni->prev->next = ni->next;
738 } else {
739 assert(_oldest_news == ni);
740 _oldest_news = ni->next;
743 if (ni->next != nullptr) {
744 ni->next->prev = ni->prev;
745 } else {
746 assert(_latest_news == ni);
747 _latest_news = ni->prev;
750 _total_news--;
752 if (_forced_news == ni || _current_news == ni) {
753 /* When we're the current news, go to the previous item first;
754 * we just possibly made that the last news item. */
755 if (_current_news == ni) _current_news = ni->prev;
757 /* About to remove the currently forced item (shown as newspapers) ||
758 * about to remove the currently displayed item (newspapers) */
759 MoveToNextNewsItem();
762 if (_statusbar_news_item == ni) {
763 /* When we're the current news, go to the previous item first;
764 * we just possibly made that the last news item. */
765 _statusbar_news_item = ni->prev;
767 /* About to remove the currently displayed item (ticker, or just a reminder) */
768 MoveToNextTickerItem();
771 delete ni;
773 SetWindowDirty(WC_MESSAGE_HISTORY, 0);
777 * Add a new newsitem to be shown.
778 * @param string String to display
779 * @param type news category
780 * @param flags display flags for the news
781 * @param reftype1 Type of ref1
782 * @param ref1 Reference 1 to some object: Used for a possible viewport, scrolling after clicking on the news, and for deleting the news when the object is deleted.
783 * @param reftype2 Type of ref2
784 * @param ref2 Reference 2 to some object: Used for scrolling after clicking on the news, and for deleting the news when the object is deleted.
785 * @param free_data Pointer to data that must be freed once the news message is cleared
787 * @see NewsSubtype
789 void AddNewsItem(StringID string, NewsType type, NewsFlag flags, NewsReferenceType reftype1, uint32 ref1, NewsReferenceType reftype2, uint32 ref2, void *free_data)
791 if (_game_mode == GM_MENU) return;
793 /* Create new news item node */
794 NewsItem *ni = new NewsItem;
796 ni->string_id = string;
797 ni->type = type;
798 ni->flags = flags;
800 /* show this news message in colour? */
801 if (_cur_year >= _settings_client.gui.coloured_news_year) ni->flags |= NF_INCOLOUR;
803 ni->reftype1 = reftype1;
804 ni->reftype2 = reftype2;
805 ni->ref1 = ref1;
806 ni->ref2 = ref2;
807 ni->free_data = free_data;
808 ni->date = _date;
809 CopyOutDParam(ni->params, 0, lengthof(ni->params));
811 if (_total_news++ == 0) {
812 assert(_oldest_news == nullptr);
813 _oldest_news = ni;
814 ni->prev = nullptr;
815 } else {
816 assert(_latest_news->next == nullptr);
817 _latest_news->next = ni;
818 ni->prev = _latest_news;
821 ni->next = nullptr;
822 _latest_news = ni;
824 /* Keep the number of stored news items to a managable number */
825 if (_total_news > MAX_NEWS_AMOUNT) {
826 DeleteNewsItem(_oldest_news);
829 SetWindowDirty(WC_MESSAGE_HISTORY, 0);
833 * Create a new custom news item.
834 * @param tile unused
835 * @param flags type of operation
836 * @param p1 various bitstuffed elements
837 * - p1 = (bit 0 - 7) - NewsType of the message.
838 * - p1 = (bit 8 - 15) - NewsReferenceType of first reference.
839 * - p1 = (bit 16 - 23) - Company this news message is for.
840 * @param p2 First reference of the news message.
841 * @param text The text of the news message.
842 * @return the cost of this operation or an error
844 CommandCost CmdCustomNewsItem(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
846 if (_current_company != OWNER_DEITY) return CMD_ERROR;
848 NewsType type = (NewsType)GB(p1, 0, 8);
849 NewsReferenceType reftype1 = (NewsReferenceType)GB(p1, 8, 8);
850 CompanyID company = (CompanyID)GB(p1, 16, 8);
852 if (company != INVALID_OWNER && !Company::IsValidID(company)) return CMD_ERROR;
853 if (type >= NT_END) return CMD_ERROR;
854 if (StrEmpty(text)) return CMD_ERROR;
856 switch (reftype1) {
857 case NR_NONE: break;
858 case NR_TILE:
859 if (!IsValidTile(p2)) return CMD_ERROR;
860 break;
862 case NR_VEHICLE:
863 if (!Vehicle::IsValidID(p2)) return CMD_ERROR;
864 break;
866 case NR_STATION:
867 if (!Station::IsValidID(p2)) return CMD_ERROR;
868 break;
870 case NR_INDUSTRY:
871 if (!Industry::IsValidID(p2)) return CMD_ERROR;
872 break;
874 case NR_TOWN:
875 if (!Town::IsValidID(p2)) return CMD_ERROR;
876 break;
878 case NR_ENGINE:
879 if (!Engine::IsValidID(p2)) return CMD_ERROR;
880 break;
882 default: return CMD_ERROR;
885 if (company != INVALID_OWNER && company != _local_company) return CommandCost();
887 if (flags & DC_EXEC) {
888 char *news = stredup(text);
889 SetDParamStr(0, news);
890 AddNewsItem(STR_NEWS_CUSTOM_ITEM, type, NF_NORMAL, reftype1, p2, NR_NONE, UINT32_MAX, news);
893 return CommandCost();
897 * Delete a news item type about a vehicle.
898 * When the news item type is INVALID_STRING_ID all news about the vehicle gets deleted.
899 * @param vid The vehicle to remove the news for.
900 * @param news The news type to remove.
902 void DeleteVehicleNews(VehicleID vid, StringID news)
904 NewsItem *ni = _oldest_news;
906 while (ni != nullptr) {
907 NewsItem *next = ni->next;
908 if (((ni->reftype1 == NR_VEHICLE && ni->ref1 == vid) || (ni->reftype2 == NR_VEHICLE && ni->ref2 == vid)) &&
909 (news == INVALID_STRING_ID || ni->string_id == news)) {
910 DeleteNewsItem(ni);
912 ni = next;
917 * Remove news regarding given station so there are no 'unknown station now accepts Mail'
918 * or 'First train arrived at unknown station' news items.
919 * @param sid station to remove news about
921 void DeleteStationNews(StationID sid)
923 NewsItem *ni = _oldest_news;
925 while (ni != nullptr) {
926 NewsItem *next = ni->next;
927 if ((ni->reftype1 == NR_STATION && ni->ref1 == sid) || (ni->reftype2 == NR_STATION && ni->ref2 == sid)) {
928 DeleteNewsItem(ni);
930 ni = next;
935 * Remove news regarding given industry
936 * @param iid industry to remove news about
938 void DeleteIndustryNews(IndustryID iid)
940 NewsItem *ni = _oldest_news;
942 while (ni != nullptr) {
943 NewsItem *next = ni->next;
944 if ((ni->reftype1 == NR_INDUSTRY && ni->ref1 == iid) || (ni->reftype2 == NR_INDUSTRY && ni->ref2 == iid)) {
945 DeleteNewsItem(ni);
947 ni = next;
952 * Remove engine announcements for invalid engines.
954 void DeleteInvalidEngineNews()
956 NewsItem *ni = _oldest_news;
958 while (ni != nullptr) {
959 NewsItem *next = ni->next;
960 if ((ni->reftype1 == NR_ENGINE && (!Engine::IsValidID(ni->ref1) || !Engine::Get(ni->ref1)->IsEnabled())) ||
961 (ni->reftype2 == NR_ENGINE && (!Engine::IsValidID(ni->ref2) || !Engine::Get(ni->ref2)->IsEnabled()))) {
962 DeleteNewsItem(ni);
964 ni = next;
968 static void RemoveOldNewsItems()
970 NewsItem *next;
971 for (NewsItem *cur = _oldest_news; _total_news > MIN_NEWS_AMOUNT && cur != nullptr; cur = next) {
972 next = cur->next;
973 if (_date - _news_type_data[cur->type].age * _settings_client.gui.news_message_timeout > cur->date) DeleteNewsItem(cur);
978 * Report a change in vehicle IDs (due to autoreplace) to affected vehicle news.
979 * @note Viewports of currently displayed news is changed via #ChangeVehicleViewports
980 * @param from_index the old vehicle ID
981 * @param to_index the new vehicle ID
983 void ChangeVehicleNews(VehicleID from_index, VehicleID to_index)
985 for (NewsItem *ni = _oldest_news; ni != nullptr; ni = ni->next) {
986 if (ni->reftype1 == NR_VEHICLE && ni->ref1 == from_index) ni->ref1 = to_index;
987 if (ni->reftype2 == NR_VEHICLE && ni->ref2 == from_index) ni->ref2 = to_index;
988 if (ni->flags & NF_VEHICLE_PARAM0 && ni->params[0] == from_index) ni->params[0] = to_index;
992 void NewsLoop()
994 /* no news item yet */
995 if (_total_news == 0) return;
997 static byte _last_clean_month = 0;
999 if (_last_clean_month != _cur_month) {
1000 RemoveOldNewsItems();
1001 _last_clean_month = _cur_month;
1004 if (ReadyForNextTickerItem()) MoveToNextTickerItem();
1005 if (ReadyForNextNewsItem()) MoveToNextNewsItem();
1008 /** Do a forced show of a specific message */
1009 static void ShowNewsMessage(const NewsItem *ni)
1011 assert(_total_news != 0);
1013 /* Delete the news window */
1014 DeleteWindowById(WC_NEWS_WINDOW, 0);
1016 /* setup forced news item */
1017 _forced_news = ni;
1019 if (_forced_news != nullptr) {
1020 DeleteWindowById(WC_NEWS_WINDOW, 0);
1021 ShowNewspaper(ni);
1026 * Close active news message window
1027 * @return true if a window was closed.
1029 bool HideActiveNewsMessage() {
1030 NewsWindow *w = (NewsWindow*)FindWindowById(WC_NEWS_WINDOW, 0);
1031 if (w == nullptr) return false;
1032 delete w;
1033 return true;
1036 /** Show previous news item */
1037 void ShowLastNewsMessage()
1039 const NewsItem *ni = nullptr;
1040 if (_total_news == 0) {
1041 return;
1042 } else if (_forced_news == nullptr) {
1043 /* Not forced any news yet, show the current one, unless a news window is
1044 * open (which can only be the current one), then show the previous item */
1045 if (_current_news == nullptr) {
1046 /* No news were shown yet resp. the last shown one was already deleted.
1047 * Threat this as if _forced_news reached _oldest_news; so, wrap around and start anew with the latest. */
1048 ni = _latest_news;
1049 } else {
1050 const Window *w = FindWindowById(WC_NEWS_WINDOW, 0);
1051 ni = (w == nullptr || (_current_news == _oldest_news)) ? _current_news : _current_news->prev;
1053 } else if (_forced_news == _oldest_news) {
1054 /* We have reached the oldest news, start anew with the latest */
1055 ni = _latest_news;
1056 } else {
1057 /* 'Scrolling' through news history show each one in turn */
1058 ni = _forced_news->prev;
1060 bool wrap = false;
1061 for (;;) {
1062 if (_news_type_data[ni->type].GetDisplay() != ND_OFF) {
1063 ShowNewsMessage(ni);
1064 break;
1067 ni = ni->prev;
1068 if (ni == nullptr) {
1069 if (wrap) break;
1070 /* We have reached the oldest news, start anew with the latest */
1071 ni = _latest_news;
1072 wrap = true;
1079 * Draw an unformatted news message truncated to a maximum length. If
1080 * length exceeds maximum length it will be postfixed by '...'
1081 * @param left the left most location for the string
1082 * @param right the right most location for the string
1083 * @param y position of the string
1084 * @param colour the colour the string will be shown in
1085 * @param *ni NewsItem being printed
1087 static void DrawNewsString(uint left, uint right, int y, TextColour colour, const NewsItem *ni)
1089 char buffer[512], buffer2[512];
1090 StringID str;
1092 CopyInDParam(0, ni->params, lengthof(ni->params));
1093 str = ni->string_id;
1095 GetString(buffer, str, lastof(buffer));
1096 /* Copy the just gotten string to another buffer to remove any formatting
1097 * from it such as big fonts, etc. */
1098 const char *ptr = buffer;
1099 char *dest = buffer2;
1100 WChar c_last = '\0';
1101 for (;;) {
1102 WChar c = Utf8Consume(&ptr);
1103 if (c == 0) break;
1104 /* Make a space from a newline, but ignore multiple newlines */
1105 if (c == '\n' && c_last != '\n') {
1106 dest[0] = ' ';
1107 dest++;
1108 } else if (c == '\r') {
1109 dest[0] = dest[1] = dest[2] = dest[3] = ' ';
1110 dest += 4;
1111 } else if (IsPrintable(c)) {
1112 dest += Utf8Encode(dest, c);
1114 c_last = c;
1117 *dest = '\0';
1118 /* Truncate and show string; postfixed by '...' if necessary */
1119 DrawString(left, right, y, buffer2, colour);
1122 struct MessageHistoryWindow : Window {
1123 static const int top_spacing; ///< Additional spacing at the top of the #WID_MH_BACKGROUND widget.
1124 static const int bottom_spacing; ///< Additional spacing at the bottom of the #WID_MH_BACKGROUND widget.
1126 int line_height; /// < Height of a single line in the news history window including spacing.
1127 int date_width; /// < Width needed for the date part.
1129 Scrollbar *vscroll;
1131 MessageHistoryWindow(WindowDesc *desc) : Window(desc)
1133 this->CreateNestedTree();
1134 this->vscroll = this->GetScrollbar(WID_MH_SCROLLBAR);
1135 this->FinishInitNested(); // Initializes 'this->line_height' and 'this->date_width'.
1136 this->OnInvalidateData(0);
1139 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
1141 if (widget == WID_MH_BACKGROUND) {
1142 this->line_height = FONT_HEIGHT_NORMAL + 2;
1143 resize->height = this->line_height;
1145 /* Months are off-by-one, so it's actually 8. Not using
1146 * month 12 because the 1 is usually less wide. */
1147 SetDParam(0, ConvertYMDToDate(ORIGINAL_MAX_YEAR, 7, 30));
1148 this->date_width = GetStringBoundingBox(STR_SHORT_DATE).width;
1150 size->height = 4 * resize->height + this->top_spacing + this->bottom_spacing; // At least 4 lines are visible.
1151 size->width = std::max(200u, size->width); // At least 200 pixels wide.
1155 void OnPaint() override
1157 this->OnInvalidateData(0);
1158 this->DrawWidgets();
1161 void DrawWidget(const Rect &r, int widget) const override
1163 if (widget != WID_MH_BACKGROUND || _total_news == 0) return;
1165 /* Find the first news item to display. */
1166 NewsItem *ni = _latest_news;
1167 for (int n = this->vscroll->GetPosition(); n > 0; n--) {
1168 ni = ni->prev;
1169 if (ni == nullptr) return;
1172 /* Fill the widget with news items. */
1173 int y = r.top + this->top_spacing;
1174 bool rtl = _current_text_dir == TD_RTL;
1175 uint date_left = rtl ? r.right - WD_FRAMERECT_RIGHT - this->date_width : r.left + WD_FRAMERECT_LEFT;
1176 uint date_right = rtl ? r.right - WD_FRAMERECT_RIGHT : r.left + WD_FRAMERECT_LEFT + this->date_width;
1177 uint news_left = rtl ? r.left + WD_FRAMERECT_LEFT : r.left + WD_FRAMERECT_LEFT + this->date_width + WD_FRAMERECT_RIGHT;
1178 uint news_right = rtl ? r.right - WD_FRAMERECT_RIGHT - this->date_width - WD_FRAMERECT_RIGHT : r.right - WD_FRAMERECT_RIGHT;
1179 for (int n = this->vscroll->GetCapacity(); n > 0; n--) {
1180 SetDParam(0, ni->date);
1181 DrawString(date_left, date_right, y, STR_SHORT_DATE);
1183 DrawNewsString(news_left, news_right, y, TC_WHITE, ni);
1184 y += this->line_height;
1186 ni = ni->prev;
1187 if (ni == nullptr) return;
1192 * Some data on this window has become invalid.
1193 * @param data Information about the changed data.
1194 * @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.
1196 void OnInvalidateData(int data = 0, bool gui_scope = true) override
1198 if (!gui_scope) return;
1199 this->vscroll->SetCount(_total_news);
1202 void OnClick(Point pt, int widget, int click_count) override
1204 if (widget == WID_MH_BACKGROUND) {
1205 NewsItem *ni = _latest_news;
1206 if (ni == nullptr) return;
1208 for (int n = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_MH_BACKGROUND, WD_FRAMERECT_TOP, this->line_height); n > 0; n--) {
1209 ni = ni->prev;
1210 if (ni == nullptr) return;
1213 ShowNewsMessage(ni);
1217 void OnResize() override
1219 this->vscroll->SetCapacityFromWidget(this, WID_MH_BACKGROUND);
1223 const int MessageHistoryWindow::top_spacing = WD_FRAMERECT_TOP + 4;
1224 const int MessageHistoryWindow::bottom_spacing = WD_FRAMERECT_BOTTOM;
1226 static const NWidgetPart _nested_message_history[] = {
1227 NWidget(NWID_HORIZONTAL),
1228 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1229 NWidget(WWT_CAPTION, COLOUR_BROWN), SetDataTip(STR_MESSAGE_HISTORY, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1230 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1231 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1232 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1233 EndContainer(),
1235 NWidget(NWID_HORIZONTAL),
1236 NWidget(WWT_PANEL, COLOUR_BROWN, WID_MH_BACKGROUND), SetMinimalSize(200, 125), SetDataTip(0x0, STR_MESSAGE_HISTORY_TOOLTIP), SetResize(1, 12), SetScrollbar(WID_MH_SCROLLBAR),
1237 EndContainer(),
1238 NWidget(NWID_VERTICAL),
1239 NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_MH_SCROLLBAR),
1240 NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
1241 EndContainer(),
1242 EndContainer(),
1245 static WindowDesc _message_history_desc(
1246 WDP_AUTO, "list_news", 400, 140,
1247 WC_MESSAGE_HISTORY, WC_NONE,
1249 _nested_message_history, lengthof(_nested_message_history)
1252 /** Display window with news messages history */
1253 void ShowMessageHistory()
1255 DeleteWindowById(WC_MESSAGE_HISTORY, 0);
1256 new MessageHistoryWindow(&_message_history_desc);