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/>.
10 /** @file news_gui.cpp GUI functions related to news messages. */
14 #include "viewport_func.h"
15 #include "news_type.h"
16 #include "strings_func.h"
17 #include "window_func.h"
18 #include "date_func.h"
19 #include "vehicle_base.h"
20 #include "vehicle_func.h"
21 #include "vehicle_gui.h"
22 #include "station_base.h"
25 #include "sound_func.h"
26 #include "string_func.h"
27 #include "widgets/dropdown_func.h"
28 #include "statusbar_gui.h"
29 #include "company_manager_face.h"
30 #include "company_func.h"
31 #include "engine_base.h"
32 #include "engine_gui.h"
33 #include "core/geometry_func.hpp"
35 #include "table/strings.h"
37 const NewsItem
*_statusbar_news_item
= NULL
;
38 bool _news_ticker_sound
; ///< Make a ticker sound when a news item is published.
40 static uint MIN_NEWS_AMOUNT
= 30; ///< prefered minimum amount of news messages
41 static uint _total_news
= 0; ///< current number of news items
42 static NewsItem
*_oldest_news
= NULL
; ///< head of news items queue
43 static NewsItem
*_latest_news
= NULL
; ///< tail of news items queue
47 * Users can force an item by accessing the history or "last message".
48 * If the message being shown was forced by the user, a pointer is stored
49 * in _forced_news. Otherwise, \a _forced_news variable is NULL.
51 static const NewsItem
*_forced_news
= NULL
; ///< item the user has asked for
53 /** Current news item (last item shown regularly). */
54 static const NewsItem
*_current_news
= NULL
;
58 * Get the position a news-reference is referencing.
59 * @param reftype The type of reference.
60 * @param ref The reference.
61 * @return A tile for the referenced object, or INVALID_TILE if none.
63 static TileIndex
GetReferenceTile(NewsReferenceType reftype
, uint32 ref
)
66 case NR_TILE
: return (TileIndex
)ref
;
67 case NR_STATION
: return Station::Get((StationID
)ref
)->xy
;
68 case NR_INDUSTRY
: return Industry::Get((IndustryID
)ref
)->location
.tile
+ TileDiffXY(1, 1);
69 case NR_TOWN
: return Town::Get((TownID
)ref
)->xy
;
70 default: return INVALID_TILE
;
74 /** Widget numbers of the news display windows. */
75 enum NewsTypeWidgets
{
76 NTW_PANEL
, ///< The news item background panel.
77 NTW_TITLE
, ///< Title of the company news.
78 NTW_HEADLINE
, ///< The news headline.
79 NTW_CLOSEBOX
, ///< Close the window.
80 NTW_DATE
, ///< Date of the news item.
81 NTW_CAPTION
, ///< Title bar of the window. Only used in small news items.
82 NTW_INSET
, ///< Inset around the viewport in the window. Only used in small news items.
83 NTW_VIEWPORT
, ///< Viewport in the window.
84 NTW_COMPANY_MSG
, ///< Message in company news items.
85 NTW_MESSAGE
, ///< Space for displaying the message. Only used in small news items.
86 NTW_MGR_FACE
, ///< Face of the manager.
87 NTW_MGR_NAME
, ///< Name of the manager.
88 NTW_VEH_TITLE
, ///< Vehicle new title.
89 NTW_VEH_BKGND
, ///< Dark background of new vehicle news.
90 NTW_VEH_NAME
, ///< Name of the new vehicle.
91 NTW_VEH_SPR
, ///< Graphical display of the new vehicle.
92 NTW_VEH_INFO
, ///< Some technical data of the new vehicle.
95 /* Normal news items. */
96 static const NWidgetPart _nested_normal_news_widgets
[] = {
97 NWidget(WWT_PANEL
, COLOUR_WHITE
, NTW_PANEL
),
98 NWidget(NWID_HORIZONTAL
), SetPadding(1, 1, 0, 1),
99 NWidget(WWT_TEXT
, COLOUR_WHITE
, NTW_CLOSEBOX
), SetDataTip(STR_SILVER_CROSS
, STR_NULL
), SetPadding(0, 0, 0, 1),
100 NWidget(NWID_SPACER
), SetFill(1, 0),
101 NWidget(NWID_VERTICAL
),
102 NWidget(WWT_LABEL
, COLOUR_WHITE
, NTW_DATE
), SetDataTip(STR_DATE_LONG_SMALL
, STR_NULL
),
103 NWidget(NWID_SPACER
), SetFill(0, 1),
106 NWidget(WWT_EMPTY
, COLOUR_WHITE
, NTW_MESSAGE
), SetMinimalSize(428, 154), SetPadding(0, 1, 1, 1),
110 static const WindowDesc
_normal_news_desc(
112 WC_NEWS_WINDOW
, WC_NONE
,
114 _nested_normal_news_widgets
, lengthof(_nested_normal_news_widgets
)
117 /* New vehicles news items. */
118 static const NWidgetPart _nested_vehicle_news_widgets
[] = {
119 NWidget(WWT_PANEL
, COLOUR_WHITE
, NTW_PANEL
),
120 NWidget(NWID_HORIZONTAL
), SetPadding(1, 1, 0, 1),
121 NWidget(NWID_VERTICAL
),
122 NWidget(WWT_TEXT
, COLOUR_WHITE
, NTW_CLOSEBOX
), SetDataTip(STR_SILVER_CROSS
, STR_NULL
), SetPadding(0, 0, 0, 1),
123 NWidget(NWID_SPACER
), SetFill(0, 1),
125 NWidget(WWT_LABEL
, COLOUR_WHITE
, NTW_VEH_TITLE
), SetFill(1, 1), SetMinimalSize(419, 55), SetDataTip(STR_EMPTY
, STR_NULL
),
127 NWidget(WWT_PANEL
, COLOUR_WHITE
, NTW_VEH_BKGND
), SetPadding(0, 25, 1, 25),
128 NWidget(NWID_VERTICAL
),
129 NWidget(WWT_EMPTY
, INVALID_COLOUR
, NTW_VEH_NAME
), SetMinimalSize(369, 33), SetFill(1, 0),
130 NWidget(WWT_EMPTY
, INVALID_COLOUR
, NTW_VEH_SPR
), SetMinimalSize(369, 32), SetFill(1, 0),
131 NWidget(WWT_EMPTY
, INVALID_COLOUR
, NTW_VEH_INFO
), SetMinimalSize(369, 46), SetFill(1, 0),
137 static const WindowDesc
_vehicle_news_desc(
139 WC_NEWS_WINDOW
, WC_NONE
,
141 _nested_vehicle_news_widgets
, lengthof(_nested_vehicle_news_widgets
)
144 /* Company news items. */
145 static const NWidgetPart _nested_company_news_widgets
[] = {
146 NWidget(WWT_PANEL
, COLOUR_WHITE
, NTW_PANEL
),
147 NWidget(NWID_HORIZONTAL
), SetPadding(1, 1, 0, 1),
148 NWidget(NWID_VERTICAL
),
149 NWidget(WWT_TEXT
, COLOUR_WHITE
, NTW_CLOSEBOX
), SetDataTip(STR_SILVER_CROSS
, STR_NULL
), SetPadding(0, 0, 0, 1),
150 NWidget(NWID_SPACER
), SetFill(0, 1),
152 NWidget(WWT_LABEL
, COLOUR_WHITE
, NTW_TITLE
), SetFill(1, 1), SetMinimalSize(410, 20), SetDataTip(STR_EMPTY
, STR_NULL
),
154 NWidget(NWID_HORIZONTAL
), SetPadding(0, 1, 1, 1),
155 NWidget(NWID_VERTICAL
),
156 NWidget(WWT_EMPTY
, COLOUR_WHITE
, NTW_MGR_FACE
), SetMinimalSize(93, 119), SetPadding(2, 6, 2, 1),
157 NWidget(NWID_HORIZONTAL
),
158 NWidget(WWT_EMPTY
, COLOUR_WHITE
, NTW_MGR_NAME
), SetMinimalSize(93, 24), SetPadding(0, 0, 0, 1),
159 NWidget(NWID_SPACER
), SetFill(1, 0),
161 NWidget(NWID_SPACER
), SetFill(0, 1),
163 NWidget(WWT_EMPTY
, COLOUR_WHITE
, NTW_COMPANY_MSG
), SetFill(1, 1), SetMinimalSize(328, 150),
168 static const WindowDesc
_company_news_desc(
170 WC_NEWS_WINDOW
, WC_NONE
,
172 _nested_company_news_widgets
, lengthof(_nested_company_news_widgets
)
175 /* Thin news items. */
176 static const NWidgetPart _nested_thin_news_widgets
[] = {
177 NWidget(WWT_PANEL
, COLOUR_WHITE
, NTW_PANEL
),
178 NWidget(NWID_HORIZONTAL
), SetPadding(1, 1, 0, 1),
179 NWidget(WWT_TEXT
, COLOUR_WHITE
, NTW_CLOSEBOX
), SetDataTip(STR_SILVER_CROSS
, STR_NULL
), SetPadding(0, 0, 0, 1),
180 NWidget(NWID_SPACER
), SetFill(1, 0),
181 NWidget(NWID_VERTICAL
),
182 NWidget(WWT_LABEL
, COLOUR_WHITE
, NTW_DATE
), SetDataTip(STR_DATE_LONG_SMALL
, STR_NULL
),
183 NWidget(NWID_SPACER
), SetFill(0, 1),
186 NWidget(WWT_EMPTY
, COLOUR_WHITE
, NTW_MESSAGE
), SetMinimalSize(428, 48), SetFill(1, 0), SetPadding(0, 1, 0, 1),
187 NWidget(NWID_VIEWPORT
, INVALID_COLOUR
, NTW_VIEWPORT
), SetMinimalSize(426, 70), SetPadding(1, 2, 2, 2),
191 static const WindowDesc
_thin_news_desc(
193 WC_NEWS_WINDOW
, WC_NONE
,
195 _nested_thin_news_widgets
, lengthof(_nested_thin_news_widgets
)
198 /* Small news items. */
199 static const NWidgetPart _nested_small_news_widgets
[] = {
200 /* Caption + close box. The caption is no WWT_CAPTION as the window shall not be moveable and so on. */
201 NWidget(NWID_HORIZONTAL
),
202 NWidget(WWT_CLOSEBOX
, COLOUR_LIGHT_BLUE
, NTW_CLOSEBOX
),
203 NWidget(WWT_EMPTY
, COLOUR_LIGHT_BLUE
, NTW_CAPTION
), SetFill(1, 0),
207 NWidget(WWT_PANEL
, COLOUR_LIGHT_BLUE
, NTW_HEADLINE
),
208 NWidget(WWT_INSET
, COLOUR_LIGHT_BLUE
, NTW_INSET
), SetPadding(2, 2, 2, 2),
209 NWidget(NWID_VIEWPORT
, INVALID_COLOUR
, NTW_VIEWPORT
), SetPadding(1, 1, 1, 1), SetMinimalSize(274, 47), SetFill(1, 0),
211 NWidget(WWT_EMPTY
, COLOUR_WHITE
, NTW_MESSAGE
), SetMinimalSize(275, 20), SetFill(1, 0),
215 static const WindowDesc
_small_news_desc(
217 WC_NEWS_WINDOW
, WC_NONE
,
219 _nested_small_news_widgets
, lengthof(_nested_small_news_widgets
)
223 * Data common to all news items of a given subtype (structure)
225 struct NewsSubtypeData
{
226 NewsType type
; ///< News category @see NewsType
227 NewsFlag flags
; ///< Initial NewsFlags bits @see NewsFlag
228 const WindowDesc
*desc
; ///< Window description for displaying this news.
232 * Data common to all news items of a given subtype (actual data)
234 static const NewsSubtypeData _news_subtype_data
[] = {
235 /* type, display_mode, flags, window description, callback */
236 { NT_ARRIVAL_COMPANY
, (NF_NO_TRANSPARENT
| NF_SHADE
), &_thin_news_desc
}, ///< NS_ARRIVAL_COMPANY
237 { NT_ARRIVAL_OTHER
, (NF_NO_TRANSPARENT
| NF_SHADE
), &_thin_news_desc
}, ///< NS_ARRIVAL_OTHER
238 { NT_ACCIDENT
, (NF_NO_TRANSPARENT
| NF_SHADE
), &_thin_news_desc
}, ///< NS_ACCIDENT
239 { NT_COMPANY_INFO
, NF_NONE
, &_company_news_desc
}, ///< NS_COMPANY_TROUBLE
240 { NT_COMPANY_INFO
, NF_NONE
, &_company_news_desc
}, ///< NS_COMPANY_MERGER
241 { NT_COMPANY_INFO
, NF_NONE
, &_company_news_desc
}, ///< NS_COMPANY_BANKRUPT
242 { NT_COMPANY_INFO
, NF_NONE
, &_company_news_desc
}, ///< NS_COMPANY_NEW
243 { NT_INDUSTRY_OPEN
, (NF_NO_TRANSPARENT
| NF_SHADE
), &_thin_news_desc
}, ///< NS_INDUSTRY_OPEN
244 { NT_INDUSTRY_CLOSE
, (NF_NO_TRANSPARENT
| NF_SHADE
), &_thin_news_desc
}, ///< NS_INDUSTRY_CLOSE
245 { NT_ECONOMY
, NF_NONE
, &_normal_news_desc
}, ///< NS_ECONOMY
246 { NT_INDUSTRY_COMPANY
, (NF_NO_TRANSPARENT
| NF_SHADE
), &_thin_news_desc
}, ///< NS_INDUSTRY_COMPANY
247 { NT_INDUSTRY_OTHER
, (NF_NO_TRANSPARENT
| NF_SHADE
), &_thin_news_desc
}, ///< NS_INDUSTRY_OTHER
248 { NT_INDUSTRY_NOBODY
, (NF_NO_TRANSPARENT
| NF_SHADE
), &_thin_news_desc
}, ///< NS_INDUSTRY_NOBODY
249 { NT_ADVICE
, NF_INCOLOUR
, &_small_news_desc
}, ///< NS_ADVICE
250 { NT_NEW_VEHICLES
, NF_NONE
, &_vehicle_news_desc
}, ///< NS_NEW_VEHICLES
251 { NT_ACCEPTANCE
, NF_INCOLOUR
, &_small_news_desc
}, ///< NS_ACCEPTANCE
252 { NT_SUBSIDIES
, NF_NONE
, &_normal_news_desc
}, ///< NS_SUBSIDIES
253 { NT_GENERAL
, NF_NONE
, &_normal_news_desc
}, ///< NS_GENERAL
256 assert_compile(lengthof(_news_subtype_data
) == NS_END
);
261 NewsTypeData _news_type_data
[] = {
262 /* name, age, sound, display, description */
263 NewsTypeData("arrival_player", 60, SND_1D_APPLAUSE
, ND_FULL
, STR_NEWS_MESSAGE_TYPE_ARRIVAL_OF_FIRST_VEHICLE_OWN
), ///< NT_ARRIVAL_COMPANY
264 NewsTypeData("arrival_other", 60, SND_1D_APPLAUSE
, ND_SUMMARY
, STR_NEWS_MESSAGE_TYPE_ARRIVAL_OF_FIRST_VEHICLE_OTHER
), ///< NT_ARRIVAL_OTHER
265 NewsTypeData("accident", 90, SND_BEGIN
, ND_FULL
, STR_NEWS_MESSAGE_TYPE_ACCIDENTS_DISASTERS
), ///< NT_ACCIDENT
266 NewsTypeData("company_info", 60, SND_BEGIN
, ND_FULL
, STR_NEWS_MESSAGE_TYPE_COMPANY_INFORMATION
), ///< NT_COMPANY_INFO
267 NewsTypeData("open", 90, SND_BEGIN
, ND_SUMMARY
, STR_NEWS_MESSAGE_TYPE_INDUSTRY_OPEN
), ///< NT_INDUSTRY_OPEN
268 NewsTypeData("close", 90, SND_BEGIN
, ND_SUMMARY
, STR_NEWS_MESSAGE_TYPE_INDUSTRY_CLOSE
), ///< NT_INDUSTRY_CLOSE
269 NewsTypeData("economy", 30, SND_BEGIN
, ND_FULL
, STR_NEWS_MESSAGE_TYPE_ECONOMY_CHANGES
), ///< NT_ECONOMY
270 NewsTypeData("production_player", 30, SND_BEGIN
, ND_SUMMARY
, STR_NEWS_MESSAGE_TYPE_INDUSTRY_CHANGES_SERVED_BY_COMPANY
), ///< NT_INDUSTRY_COMPANY
271 NewsTypeData("production_other", 30, SND_BEGIN
, ND_OFF
, STR_NEWS_MESSAGE_TYPE_INDUSTRY_CHANGES_SERVED_BY_OTHER
), ///< NT_INDUSTRY_OTHER
272 NewsTypeData("production_nobody", 30, SND_BEGIN
, ND_OFF
, STR_NEWS_MESSAGE_TYPE_INDUSTRY_CHANGES_UNSERVED
), ///< NT_INDUSTRY_NOBODY
273 NewsTypeData("advice", 150, SND_BEGIN
, ND_FULL
, STR_NEWS_MESSAGE_TYPE_ADVICE_INFORMATION_ON_COMPANY
), ///< NT_ADVICE
274 NewsTypeData("new_vehicles", 30, SND_1E_OOOOH
, ND_FULL
, STR_NEWS_MESSAGE_TYPE_NEW_VEHICLES
), ///< NT_NEW_VEHICLES
275 NewsTypeData("acceptance", 90, SND_BEGIN
, ND_FULL
, STR_NEWS_MESSAGE_TYPE_CHANGES_OF_CARGO_ACCEPTANCE
), ///< NT_ACCEPTANCE
276 NewsTypeData("subsidies", 180, SND_BEGIN
, ND_SUMMARY
, STR_NEWS_MESSAGE_TYPE_SUBSIDIES
), ///< NT_SUBSIDIES
277 NewsTypeData("general", 60, SND_BEGIN
, ND_FULL
, STR_NEWS_MESSAGE_TYPE_GENERAL_INFORMATION
), ///< NT_GENERAL
280 assert_compile(lengthof(_news_type_data
) == NT_END
);
282 /** Window class displaying a news item. */
283 struct NewsWindow
: Window
{
284 uint16 chat_height
; ///< Height of the chat window.
285 uint16 status_height
; ///< Height of the status bar window
286 const NewsItem
*ni
; ///< News item to display.
287 static uint duration
; ///< Remaining time for showing current news message (may only be accessed while a news item is displayed).
289 NewsWindow(const WindowDesc
*desc
, const NewsItem
*ni
) : Window(), ni(ni
)
291 NewsWindow::duration
= 555;
292 const Window
*w
= FindWindowByClass(WC_SEND_NETWORK_MSG
);
293 this->chat_height
= (w
!= NULL
) ? w
->height
: 0;
294 this->status_height
= FindWindowById(WC_STATUS_BAR
, 0)->height
;
296 this->flags4
|= WF_DISABLE_VP_SCROLL
;
298 this->CreateNestedTree(desc
);
299 switch (this->ni
->subtype
) {
300 case NS_COMPANY_TROUBLE
:
301 this->GetWidget
<NWidgetCore
>(NTW_TITLE
)->widget_data
= STR_NEWS_COMPANY_IN_TROUBLE_TITLE
;
304 case NS_COMPANY_MERGER
:
305 this->GetWidget
<NWidgetCore
>(NTW_TITLE
)->widget_data
= STR_NEWS_COMPANY_MERGER_TITLE
;
308 case NS_COMPANY_BANKRUPT
:
309 this->GetWidget
<NWidgetCore
>(NTW_TITLE
)->widget_data
= STR_NEWS_COMPANY_BANKRUPT_TITLE
;
313 this->GetWidget
<NWidgetCore
>(NTW_TITLE
)->widget_data
= STR_NEWS_COMPANY_LAUNCH_TITLE
;
319 this->FinishInitNested(desc
, 0);
321 /* Initialize viewport if it exists. */
322 NWidgetViewport
*nvp
= this->GetWidget
<NWidgetViewport
>(NTW_VIEWPORT
);
324 nvp
->InitializeViewport(this, ni
->reftype1
== NR_VEHICLE
? 0x80000000 | ni
->ref1
: GetReferenceTile(ni
->reftype1
, ni
->ref1
), ZOOM_LVL_NEWS
);
325 if (this->ni
->flags
& NF_NO_TRANSPARENT
) nvp
->disp_flags
|= ND_NO_TRANSPARENCY
;
326 if ((this->ni
->flags
& NF_INCOLOUR
) == 0) {
327 nvp
->disp_flags
|= ND_SHADE_GREY
;
328 } else if (this->ni
->flags
& NF_SHADE
) {
329 nvp
->disp_flags
|= ND_SHADE_DIMMED
;
333 PositionNewsMessage(this);
336 void DrawNewsBorder(const Rect
&r
) const
338 GfxFillRect(r
.left
, r
.top
, r
.right
, r
.bottom
, PC_WHITE
);
340 GfxFillRect(r
.left
, r
.top
, r
.left
, r
.bottom
, PC_BLACK
);
341 GfxFillRect(r
.right
, r
.top
, r
.right
, r
.bottom
, PC_BLACK
);
342 GfxFillRect(r
.left
, r
.top
, r
.right
, r
.top
, PC_BLACK
);
343 GfxFillRect(r
.left
, r
.bottom
, r
.right
, r
.bottom
, PC_BLACK
);
346 virtual Point
OnInitialPosition(const WindowDesc
*desc
, int16 sm_width
, int16 sm_height
, int window_number
)
348 Point pt
= { 0, _screen
.height
};
352 virtual void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
)
354 StringID str
= STR_NULL
;
357 CopyInDParam(0, this->ni
->params
, lengthof(this->ni
->params
));
358 str
= this->ni
->string_id
;
361 case NTW_COMPANY_MSG
:
362 str
= this->GetCompanyMessageString();
367 str
= this->GetNewVehicleMessageString(widget
);
371 assert(this->ni
->reftype1
== NR_ENGINE
);
372 EngineID engine
= this->ni
->ref1
;
373 str
= GetEngineInfoString(engine
);
377 return; // Do nothing
380 /* Update minimal size with length of the multi-line string. */
382 d
.width
= (d
.width
>= padding
.width
) ? d
.width
- padding
.width
: 0;
383 d
.height
= (d
.height
>= padding
.height
) ? d
.height
- padding
.height
: 0;
384 d
= GetStringMultiLineBoundingBox(str
, d
);
385 d
.width
+= padding
.width
;
386 d
.height
+= padding
.height
;
387 *size
= maxdim(*size
, d
);
390 virtual void SetStringParameters(int widget
) const
392 if (widget
== NTW_DATE
) SetDParam(0, this->ni
->date
);
395 virtual void DrawWidget(const Rect
&r
, int widget
) const
399 DrawCaption(r
, COLOUR_LIGHT_BLUE
, this->owner
, STR_NEWS_MESSAGE_CAPTION
);
403 this->DrawNewsBorder(r
);
407 CopyInDParam(0, this->ni
->params
, lengthof(this->ni
->params
));
408 DrawStringMultiLine(r
.left
+ 2, r
.right
- 2, r
.top
, r
.bottom
, this->ni
->string_id
, TC_FROMSTRING
, SA_CENTER
);
412 const CompanyNewsInformation
*cni
= (const CompanyNewsInformation
*)this->ni
->free_data
;
413 DrawCompanyManagerFace(cni
->face
, cni
->colour
, r
.left
, r
.top
);
414 GfxFillRect(r
.left
+ 1, r
.top
, r
.left
+ 1 + 91, r
.top
+ 118, PALETTE_NEWSPAPER
, FILLRECT_RECOLOUR
);
418 const CompanyNewsInformation
*cni
= (const CompanyNewsInformation
*)this->ni
->free_data
;
419 SetDParamStr(0, cni
->president_name
);
420 DrawStringMultiLine(r
.left
, r
.right
, r
.top
, r
.bottom
, STR_JUST_RAW_STRING
, TC_FROMSTRING
, SA_CENTER
);
423 case NTW_COMPANY_MSG
:
424 DrawStringMultiLine(r
.left
, r
.right
, r
.top
, r
.bottom
, this->GetCompanyMessageString(), TC_FROMSTRING
, SA_CENTER
);
428 GfxFillRect(r
.left
, r
.top
, r
.right
, r
.bottom
, PC_GREY
);
433 DrawStringMultiLine(r
.left
, r
.right
, r
.top
, r
.bottom
, this->GetNewVehicleMessageString(widget
), TC_FROMSTRING
, SA_CENTER
);
437 assert(this->ni
->reftype1
== NR_ENGINE
);
438 EngineID engine
= this->ni
->ref1
;
439 DrawVehicleEngine(r
.left
, r
.right
, (r
.left
+ r
.right
) / 2, (r
.top
+ r
.bottom
) / 2, engine
, GetEnginePalette(engine
, _local_company
));
440 GfxFillRect(r
.left
, r
.top
, r
.right
, r
.bottom
, PALETTE_NEWSPAPER
, FILLRECT_RECOLOUR
);
444 assert(this->ni
->reftype1
== NR_ENGINE
);
445 EngineID engine
= this->ni
->ref1
;
446 DrawStringMultiLine(r
.left
, r
.right
, r
.top
, r
.bottom
, GetEngineInfoString(engine
), TC_FROMSTRING
, SA_CENTER
);
452 virtual void OnClick(Point pt
, int widget
, int click_count
)
456 NewsWindow::duration
= 0;
462 if (this->ni
->reftype1
== NR_VEHICLE
) {
463 const Vehicle
*v
= Vehicle::Get(this->ni
->ref1
);
464 ShowVehicleViewWindow(v
);
469 break; // Ignore clicks
472 if (this->ni
->reftype1
== NR_VEHICLE
) {
473 const Vehicle
*v
= Vehicle::Get(this->ni
->ref1
);
474 ScrollMainWindowTo(v
->x_pos
, v
->y_pos
, v
->z_pos
);
476 TileIndex tile1
= GetReferenceTile(this->ni
->reftype1
, this->ni
->ref1
);
477 TileIndex tile2
= GetReferenceTile(this->ni
->reftype2
, this->ni
->ref2
);
479 if (tile1
!= INVALID_TILE
) ShowExtraViewPortWindow(tile1
);
480 if (tile2
!= INVALID_TILE
) ShowExtraViewPortWindow(tile2
);
482 if ((tile1
== INVALID_TILE
|| !ScrollMainWindowToTile(tile1
)) && tile2
!= INVALID_TILE
) {
483 ScrollMainWindowToTile(tile2
);
491 virtual EventState
OnKeyPress(uint16 key
, uint16 keycode
)
493 if (keycode
== WKC_SPACE
) {
494 /* Don't continue. */
498 return ES_NOT_HANDLED
;
502 * Some data on this window has become invalid.
503 * @param data Information about the changed data.
504 * @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.
506 virtual void OnInvalidateData(int data
= 0, bool gui_scope
= true)
508 if (!gui_scope
) return;
509 /* The chatbar has notified us that is was either created or closed */
510 int newtop
= this->top
+ this->chat_height
- data
;
511 this->chat_height
= data
;
512 this->SetWindowTop(newtop
);
515 virtual void OnTick()
517 /* Scroll up newsmessages from the bottom in steps of 4 pixels */
518 int newtop
= max(this->top
- 4, _screen
.height
- this->height
- this->status_height
- this->chat_height
);
519 this->SetWindowTop(newtop
);
524 * Moves the window so #newtop is new 'top' coordinate. Makes screen dirty where needed.
525 * @param newtop new top coordinate
527 void SetWindowTop(int newtop
)
529 if (this->top
== newtop
) return;
531 int mintop
= min(newtop
, this->top
);
532 int maxtop
= max(newtop
, this->top
);
533 if (this->viewport
!= NULL
) this->viewport
->top
+= newtop
- this->top
;
536 SetDirtyBlocks(this->left
, mintop
, this->left
+ this->width
, maxtop
+ this->height
);
539 StringID
GetCompanyMessageString() const
541 switch (this->ni
->subtype
) {
542 case NS_COMPANY_TROUBLE
:
543 SetDParam(0, this->ni
->params
[2]);
544 return STR_NEWS_COMPANY_IN_TROUBLE_DESCRIPTION
;
546 case NS_COMPANY_MERGER
:
547 SetDParam(0, this->ni
->params
[2]);
548 SetDParam(1, this->ni
->params
[3]);
549 SetDParam(2, this->ni
->params
[4]);
550 return this->ni
->params
[4] == 0 ? STR_NEWS_MERGER_TAKEOVER_TITLE
: STR_NEWS_COMPANY_MERGER_DESCRIPTION
;
552 case NS_COMPANY_BANKRUPT
:
553 SetDParam(0, this->ni
->params
[2]);
554 return STR_NEWS_COMPANY_BANKRUPT_DESCRIPTION
;
557 SetDParam(0, this->ni
->params
[2]);
558 SetDParam(1, this->ni
->params
[3]);
559 return STR_NEWS_COMPANY_LAUNCH_DESCRIPTION
;
566 StringID
GetNewVehicleMessageString(int widget
) const
568 assert(this->ni
->reftype1
== NR_ENGINE
);
569 EngineID engine
= this->ni
->ref1
;
573 SetDParam(0, GetEngineCategoryName(engine
));
574 return STR_NEWS_NEW_VEHICLE_NOW_AVAILABLE
;
577 SetDParam(0, engine
);
578 return STR_NEWS_NEW_VEHICLE_TYPE
;
586 /* static */ uint
NewsWindow::duration
= 0; // Instance creation.
589 /** Open up an own newspaper window for the news item */
590 static void ShowNewspaper(const NewsItem
*ni
)
592 SoundFx sound
= _news_type_data
[_news_subtype_data
[ni
->subtype
].type
].sound
;
593 if (sound
!= 0) SndPlayFx(sound
);
595 new NewsWindow(_news_subtype_data
[ni
->subtype
].desc
, ni
);
598 /** Show news item in the ticker */
599 static void ShowTicker(const NewsItem
*ni
)
601 if (_news_ticker_sound
) SndPlayFx(SND_16_MORSE
);
603 _statusbar_news_item
= ni
;
604 InvalidateWindowData(WC_STATUS_BAR
, 0, SBI_SHOW_TICKER
);
607 /** Initialize the news-items data structures */
608 void InitNewsItemStructs()
610 for (NewsItem
*ni
= _oldest_news
; ni
!= NULL
; ) {
611 NewsItem
*next
= ni
->next
;
620 _current_news
= NULL
;
621 _statusbar_news_item
= NULL
;
622 NewsWindow::duration
= 0;
626 * Are we ready to show another news item?
627 * Only if nothing is in the newsticker and no newspaper is displayed
629 static bool ReadyForNextItem()
631 const NewsItem
*ni
= _forced_news
== NULL
? _current_news
: _forced_news
;
632 if (ni
== NULL
) return true;
635 * Check if the status bar message is still being displayed? */
636 if (IsNewsTickerShown()) return false;
638 /* Newspaper message, decrement duration counter */
639 if (NewsWindow::duration
!= 0) NewsWindow::duration
--;
641 /* neither newsticker nor newspaper are running */
642 return (NewsWindow::duration
== 0 || FindWindowById(WC_NEWS_WINDOW
, 0) == NULL
);
645 /** Move to the next news item */
646 static void MoveToNextItem()
648 InvalidateWindowData(WC_STATUS_BAR
, 0, SBI_NEWS_DELETED
); // invalidate the statusbar
649 DeleteWindowById(WC_NEWS_WINDOW
, 0); // close the newspapers window if shown
651 _statusbar_news_item
= NULL
;
653 /* if we're not at the last item, then move on */
654 if (_current_news
!= _latest_news
) {
655 _current_news
= (_current_news
== NULL
) ? _oldest_news
: _current_news
->next
;
656 const NewsItem
*ni
= _current_news
;
657 const NewsType type
= _news_subtype_data
[ni
->subtype
].type
;
659 /* check the date, don't show too old items */
660 if (_date
- _news_type_data
[type
].age
> ni
->date
) return;
662 switch (_news_type_data
[type
].display
) {
663 default: NOT_REACHED();
664 case ND_OFF
: // Off - show nothing only a small reminder in the status bar
665 InvalidateWindowData(WC_STATUS_BAR
, 0, SBI_SHOW_REMINDER
);
668 case ND_SUMMARY
: // Summary - show ticker
672 case ND_FULL
: // Full - show newspaper
680 * Add a new newsitem to be shown.
681 * @param string String to display
682 * @param subtype news category, any of the NewsSubtype enums (NS_)
683 * @param reftype1 Type of ref1
684 * @param ref1 Reference 1 to some object: Used for a possible viewport, scrolling after clicking on the news, and for deleteing the news when the object is deleted.
685 * @param reftype2 Type of ref2
686 * @param ref2 Reference 2 to some object: Used for scrolling after clicking on the news, and for deleteing the news when the object is deleted.
687 * @param free_data Pointer to data that must be freed once the news message is cleared
691 void AddNewsItem(StringID string
, NewsSubtype subtype
, NewsReferenceType reftype1
, uint32 ref1
, NewsReferenceType reftype2
, uint32 ref2
, void *free_data
)
693 if (_game_mode
== GM_MENU
) return;
695 /* Create new news item node */
696 NewsItem
*ni
= new NewsItem
;
698 ni
->string_id
= string
;
699 ni
->subtype
= subtype
;
700 ni
->flags
= _news_subtype_data
[subtype
].flags
;
702 /* show this news message in colour? */
703 if (_cur_year
>= _settings_client
.gui
.coloured_news_year
) ni
->flags
|= NF_INCOLOUR
;
705 ni
->reftype1
= reftype1
;
706 ni
->reftype2
= reftype2
;
709 ni
->free_data
= free_data
;
711 CopyOutDParam(ni
->params
, 0, lengthof(ni
->params
));
713 if (_total_news
++ == 0) {
714 assert(_oldest_news
== NULL
);
718 assert(_latest_news
->next
== NULL
);
719 _latest_news
->next
= ni
;
720 ni
->prev
= _latest_news
;
726 SetWindowDirty(WC_MESSAGE_HISTORY
, 0);
729 /** Delete a news item from the queue */
730 static void DeleteNewsItem(NewsItem
*ni
)
732 /* Delete the news from the news queue. */
733 if (ni
->prev
!= NULL
) {
734 ni
->prev
->next
= ni
->next
;
736 assert(_oldest_news
== ni
);
737 _oldest_news
= ni
->next
;
740 if (ni
->next
!= NULL
) {
741 ni
->next
->prev
= ni
->prev
;
743 assert(_latest_news
== ni
);
744 _latest_news
= ni
->prev
;
749 if (_forced_news
== ni
|| _current_news
== ni
|| _statusbar_news_item
== ni
) {
750 /* When we're the current news, go to the previous item first;
751 * we just possibly made that the last news item. */
752 if (_current_news
== ni
) _current_news
= ni
->prev
;
754 /* About to remove the currently forced item (shown as newspapers) ||
755 * about to remove the currently displayed item (newspapers, ticker, or just a reminder) */
761 SetWindowDirty(WC_MESSAGE_HISTORY
, 0);
765 * Delete a news item type about a vehicle.
766 * When the news item type is INVALID_STRING_ID all news about the vehicle gets deleted.
767 * @param vid The vehicle to remove the news for.
768 * @param news The news type to remove.
770 void DeleteVehicleNews(VehicleID vid
, StringID news
)
772 NewsItem
*ni
= _oldest_news
;
775 NewsItem
*next
= ni
->next
;
776 if (((ni
->reftype1
== NR_VEHICLE
&& ni
->ref1
== vid
) || (ni
->reftype2
== NR_VEHICLE
&& ni
->ref2
== vid
)) &&
777 (news
== INVALID_STRING_ID
|| ni
->string_id
== news
)) {
785 * Remove news regarding given station so there are no 'unknown station now accepts Mail'
786 * or 'First train arrived at unknown station' news items.
787 * @param sid station to remove news about
789 void DeleteStationNews(StationID sid
)
791 NewsItem
*ni
= _oldest_news
;
794 NewsItem
*next
= ni
->next
;
795 if ((ni
->reftype1
== NR_STATION
&& ni
->ref1
== sid
) || (ni
->reftype2
== NR_STATION
&& ni
->ref2
== sid
)) {
803 * Remove news regarding given industry
804 * @param iid industry to remove news about
806 void DeleteIndustryNews(IndustryID iid
)
808 NewsItem
*ni
= _oldest_news
;
811 NewsItem
*next
= ni
->next
;
812 if ((ni
->reftype1
== NR_INDUSTRY
&& ni
->ref1
== iid
) || (ni
->reftype2
== NR_INDUSTRY
&& ni
->ref2
== iid
)) {
820 * Remove engine announcements for invalid engines.
822 void DeleteInvalidEngineNews()
824 NewsItem
*ni
= _oldest_news
;
827 NewsItem
*next
= ni
->next
;
828 if ((ni
->reftype1
== NR_ENGINE
&& (!Engine::IsValidID(ni
->ref1
) || !Engine::Get(ni
->ref1
)->IsEnabled())) ||
829 (ni
->reftype2
== NR_ENGINE
&& (!Engine::IsValidID(ni
->ref2
) || !Engine::Get(ni
->ref2
)->IsEnabled()))) {
836 static void RemoveOldNewsItems()
839 for (NewsItem
*cur
= _oldest_news
; _total_news
> MIN_NEWS_AMOUNT
&& cur
!= NULL
; cur
= next
) {
841 if (_date
- _news_type_data
[_news_subtype_data
[cur
->subtype
].type
].age
* _settings_client
.gui
.news_message_timeout
> cur
->date
) DeleteNewsItem(cur
);
846 * Report a change in vehicle IDs (due to autoreplace) to affected vehicle news.
847 * @note Viewports of currently displayed news is changed via #ChangeVehicleViewports
848 * @param from_index the old vehicle ID
849 * @param to_index the new vehicle ID
851 void ChangeVehicleNews(VehicleID from_index
, VehicleID to_index
)
853 for (NewsItem
*ni
= _oldest_news
; ni
!= NULL
; ni
= ni
->next
) {
854 if (ni
->reftype1
== NR_VEHICLE
&& ni
->ref1
== from_index
) ni
->ref1
= to_index
;
855 if (ni
->reftype2
== NR_VEHICLE
&& ni
->ref2
== from_index
) ni
->ref2
= to_index
;
858 * Autoreplace is breaking the whole news-reference concept here, as we want to keep the news,
859 * but do not know which DParams to change.
861 * Currently only NS_ADVICE news have vehicle IDs in their DParams.
862 * And all NS_ADVICE news have the ID in param 0.
864 if (ni
->subtype
== NS_ADVICE
&& ni
->params
[0] == from_index
) ni
->params
[0] = to_index
;
870 /* no news item yet */
871 if (_total_news
== 0) return;
873 /* There is no status bar, so no reason to show news;
874 * especially important with the end game screen when
875 * there is no status bar but possible news. */
876 if (FindWindowById(WC_STATUS_BAR
, 0) == NULL
) return;
878 static byte _last_clean_month
= 0;
880 if (_last_clean_month
!= _cur_month
) {
881 RemoveOldNewsItems();
882 _last_clean_month
= _cur_month
;
885 if (ReadyForNextItem()) MoveToNextItem();
888 /** Do a forced show of a specific message */
889 static void ShowNewsMessage(const NewsItem
*ni
)
891 assert(_total_news
!= 0);
893 /* Delete the news window */
894 DeleteWindowById(WC_NEWS_WINDOW
, 0);
896 /* setup forced news item */
899 if (_forced_news
!= NULL
) {
900 DeleteWindowById(WC_NEWS_WINDOW
, 0);
905 /** Show previous news item */
906 void ShowLastNewsMessage()
908 if (_total_news
== 0) {
910 } else if (_forced_news
== NULL
) {
911 /* Not forced any news yet, show the current one, unless a news window is
912 * open (which can only be the current one), then show the previous item */
913 const Window
*w
= FindWindowById(WC_NEWS_WINDOW
, 0);
914 ShowNewsMessage((w
== NULL
|| (_current_news
== _oldest_news
)) ? _current_news
: _current_news
->prev
);
915 } else if (_forced_news
== _oldest_news
) {
916 /* We have reached the oldest news, start anew with the latest */
917 ShowNewsMessage(_latest_news
);
919 /* 'Scrolling' through news history show each one in turn */
920 ShowNewsMessage(_forced_news
->prev
);
926 * Draw an unformatted news message truncated to a maximum length. If
927 * length exceeds maximum length it will be postfixed by '...'
928 * @param left the left most location for the string
929 * @param right the right most location for the string
930 * @param y position of the string
931 * @param colour the colour the string will be shown in
932 * @param *ni NewsItem being printed
933 * @param maxw maximum width of string in pixels
935 static void DrawNewsString(uint left
, uint right
, int y
, TextColour colour
, const NewsItem
*ni
)
937 char buffer
[512], buffer2
[512];
940 CopyInDParam(0, ni
->params
, lengthof(ni
->params
));
943 GetString(buffer
, str
, lastof(buffer
));
944 /* Copy the just gotten string to another buffer to remove any formatting
945 * from it such as big fonts, etc. */
946 const char *ptr
= buffer
;
947 char *dest
= buffer2
;
950 WChar c
= Utf8Consume(&ptr
);
952 /* Make a space from a newline, but ignore multiple newlines */
953 if (c
== '\n' && c_last
!= '\n') {
956 } else if (c
== '\r') {
957 dest
[0] = dest
[1] = dest
[2] = dest
[3] = ' ';
959 } else if (IsPrintable(c
)) {
960 dest
+= Utf8Encode(dest
, c
);
966 /* Truncate and show string; postfixed by '...' if neccessary */
967 DrawString(left
, right
, y
, buffer2
, colour
);
970 /** Widget numbers of the message history window. */
971 enum MessageHistoryWidgets
{
977 struct MessageHistoryWindow
: Window
{
978 static const int top_spacing
; ///< Additional spacing at the top of the #MHW_BACKGROUND widget.
979 static const int bottom_spacing
; ///< Additional spacing at the bottom of the #MHW_BACKGROUND widget.
981 int line_height
; /// < Height of a single line in the news histoy window including spacing.
982 int date_width
; /// < Width needed for the date part.
986 MessageHistoryWindow(const WindowDesc
*desc
) : Window()
988 this->CreateNestedTree(desc
);
989 this->vscroll
= this->GetScrollbar(MHW_SCROLLBAR
);
990 this->FinishInitNested(desc
); // Initializes 'this->line_height' and 'this->date_width'.
991 this->OnInvalidateData(0);
994 virtual void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
)
996 if (widget
== MHW_BACKGROUND
) {
997 this->line_height
= FONT_HEIGHT_NORMAL
+ 2;
998 resize
->height
= this->line_height
;
1000 SetDParam(0, ConvertYMDToDate(ORIGINAL_MAX_YEAR
, 12, 30));
1001 this->date_width
= GetStringBoundingBox(STR_SHORT_DATE
).width
;
1003 size
->height
= 4 * resize
->height
+ this->top_spacing
+ this->bottom_spacing
; // At least 4 lines are visible.
1004 size
->width
= max(200u, size
->width
); // At least 200 pixels wide.
1008 virtual void OnPaint()
1010 this->OnInvalidateData(0);
1011 this->DrawWidgets();
1014 virtual void DrawWidget(const Rect
&r
, int widget
) const
1016 if (widget
!= MHW_BACKGROUND
|| _total_news
== 0) return;
1018 /* Find the first news item to display. */
1019 NewsItem
*ni
= _latest_news
;
1020 for (int n
= this->vscroll
->GetPosition(); n
> 0; n
--) {
1022 if (ni
== NULL
) return;
1025 /* Fill the widget with news items. */
1026 int y
= r
.top
+ this->top_spacing
;
1027 bool rtl
= _current_text_dir
== TD_RTL
;
1028 uint date_left
= rtl
? r
.right
- WD_FRAMERECT_RIGHT
- this->date_width
: r
.left
+ WD_FRAMERECT_LEFT
;
1029 uint date_right
= rtl
? r
.right
- WD_FRAMERECT_RIGHT
: r
.left
+ WD_FRAMERECT_LEFT
+ this->date_width
;
1030 uint news_left
= rtl
? r
.left
+ WD_FRAMERECT_LEFT
: r
.left
+ WD_FRAMERECT_LEFT
+ this->date_width
+ WD_FRAMERECT_RIGHT
;
1031 uint news_right
= rtl
? r
.right
- WD_FRAMERECT_RIGHT
- this->date_width
- WD_FRAMERECT_RIGHT
: r
.right
- WD_FRAMERECT_RIGHT
;
1032 for (int n
= this->vscroll
->GetCapacity(); n
> 0; n
--) {
1033 SetDParam(0, ni
->date
);
1034 DrawString(date_left
, date_right
, y
, STR_SHORT_DATE
);
1036 DrawNewsString(news_left
, news_right
, y
, TC_WHITE
, ni
);
1037 y
+= this->line_height
;
1040 if (ni
== NULL
) return;
1045 * Some data on this window has become invalid.
1046 * @param data Information about the changed data.
1047 * @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.
1049 virtual void OnInvalidateData(int data
= 0, bool gui_scope
= true)
1051 if (!gui_scope
) return;
1052 this->vscroll
->SetCount(_total_news
);
1055 virtual void OnClick(Point pt
, int widget
, int click_count
)
1057 if (widget
== MHW_BACKGROUND
) {
1058 NewsItem
*ni
= _latest_news
;
1059 if (ni
== NULL
) return;
1061 for (int n
= this->vscroll
->GetScrolledRowFromWidget(pt
.y
, this, MHW_BACKGROUND
, WD_FRAMERECT_TOP
, this->line_height
); n
> 0; n
--) {
1063 if (ni
== NULL
) return;
1066 ShowNewsMessage(ni
);
1070 virtual void OnResize()
1072 this->vscroll
->SetCapacity(this->GetWidget
<NWidgetBase
>(MHW_BACKGROUND
)->current_y
/ this->line_height
);
1076 const int MessageHistoryWindow::top_spacing
= WD_FRAMERECT_TOP
+ 4;
1077 const int MessageHistoryWindow::bottom_spacing
= WD_FRAMERECT_BOTTOM
;
1079 static const NWidgetPart _nested_message_history
[] = {
1080 NWidget(NWID_HORIZONTAL
),
1081 NWidget(WWT_CLOSEBOX
, COLOUR_BROWN
),
1082 NWidget(WWT_CAPTION
, COLOUR_BROWN
), SetDataTip(STR_MESSAGE_HISTORY
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
1083 NWidget(WWT_SHADEBOX
, COLOUR_BROWN
),
1084 NWidget(WWT_STICKYBOX
, COLOUR_BROWN
),
1087 NWidget(NWID_HORIZONTAL
),
1088 NWidget(WWT_PANEL
, COLOUR_BROWN
, MHW_BACKGROUND
), SetMinimalSize(200, 125), SetDataTip(0x0, STR_MESSAGE_HISTORY_TOOLTIP
), SetResize(1, 12), SetScrollbar(MHW_SCROLLBAR
),
1090 NWidget(NWID_VERTICAL
),
1091 NWidget(NWID_VSCROLLBAR
, COLOUR_BROWN
, MHW_SCROLLBAR
),
1092 NWidget(WWT_RESIZEBOX
, COLOUR_BROWN
),
1097 static const WindowDesc
_message_history_desc(
1099 WC_MESSAGE_HISTORY
, WC_NONE
,
1100 WDF_UNCLICK_BUTTONS
,
1101 _nested_message_history
, lengthof(_nested_message_history
)
1104 /** Display window with news messages history */
1105 void ShowMessageHistory()
1107 DeleteWindowById(WC_MESSAGE_HISTORY
, 0);
1108 new MessageHistoryWindow(&_message_history_desc
);
1111 /** Constants in the message options window. */
1112 enum MessageOptionsSpace
{
1113 MOS_WIDG_PER_SETTING
= 4, ///< Number of widgets needed for each news category, starting at widget #WIDGET_NEWSOPT_START_OPTION.
1115 MOS_LEFT_EDGE
= 6, ///< Number of pixels between left edge of the window and the options buttons column.
1116 MOS_COLUMN_SPACING
= 4, ///< Number of pixels between the buttons and the description columns.
1117 MOS_RIGHT_EDGE
= 6, ///< Number of pixels between right edge of the window and the options descriptions column.
1118 MOS_BUTTON_SPACE
= 10, ///< Additional space in the button with the option value (for better looks).
1120 MOS_ABOVE_GLOBAL_SETTINGS
= 6, ///< Number of vertical pixels between the categories and the global options.
1121 MOS_BOTTOM_EDGE
= 6, ///< Number of pixels between bottom edge of the window and bottom of the global options.
1124 /** Message options widget numbers. */
1125 enum MessageOptionWidgets
{
1126 WIDGET_NEWSOPT_BACKGROUND
, ///< Background widget.
1127 WIDGET_NEWSOPT_LABEL
, ///< Top label.
1128 WIDGET_NEWSOPT_DROP_SUMMARY
, ///< Dropdown that adjusts at once the level for all settings.
1129 WIDGET_NEWSOPT_LABEL_SUMMARY
, ///< Label of the summary drop down.
1130 WIDGET_NEWSOPT_SOUNDTICKER
, ///< Button for (de)activating sound on events.
1131 WIDGET_NEWSOPT_SOUNDTICKER_LABEL
, ///< Label of the soundticker button,
1133 WIDGET_NEWSOPT_START_OPTION
, ///< First widget that is part of a group [<][label][>] [description]
1134 WIDGET_NEWSOPT_END_OPTION
= WIDGET_NEWSOPT_START_OPTION
+ NT_END
* MOS_WIDG_PER_SETTING
, ///< First widget after the groups.
1137 struct MessageOptionsWindow
: Window
{
1138 static const StringID message_opt
[]; ///< Message report options, 'off', 'summary', or 'full'.
1139 int state
; ///< Option value for setting all categories at once.
1140 Dimension dim_message_opt
; ///< Amount of space needed for a label such that all labels will fit.
1142 MessageOptionsWindow(const WindowDesc
*desc
) : Window()
1144 this->InitNested(desc
);
1145 /* Set up the initial disabled buttons in the case of 'off' or 'full' */
1146 NewsDisplay all_val
= _news_type_data
[0].display
;
1147 for (int i
= 0; i
< NT_END
; i
++) {
1148 this->SetMessageButtonStates(_news_type_data
[i
].display
, i
);
1149 /* If the value doesn't match the ALL-button value, set the ALL-button value to 'off' */
1150 if (_news_type_data
[i
].display
!= all_val
) all_val
= ND_OFF
;
1152 /* If all values are the same value, the ALL-button will take over this value */
1153 this->state
= all_val
;
1154 this->OnInvalidateData(0);
1158 * Setup the disabled/enabled buttons in the message window
1159 * If the value is 'off' disable the [<] widget, and enable the [>] one
1160 * Same-wise for all the others. Starting value of 4 is the first widget
1161 * group. These are grouped as [<][>] .. [<][>], etc.
1162 * @param value to set in the widget
1163 * @param element index of the group of widget to set
1165 void SetMessageButtonStates(byte value
, int element
)
1167 element
*= MOS_WIDG_PER_SETTING
;
1169 this->SetWidgetDisabledState(element
+ WIDGET_NEWSOPT_START_OPTION
, value
== 0);
1170 this->SetWidgetDisabledState(element
+ WIDGET_NEWSOPT_START_OPTION
+ 2, value
== 2);
1173 virtual void DrawWidget(const Rect
&r
, int widget
) const
1175 if (widget
>= WIDGET_NEWSOPT_START_OPTION
&& widget
< WIDGET_NEWSOPT_END_OPTION
&& (widget
- WIDGET_NEWSOPT_START_OPTION
) % MOS_WIDG_PER_SETTING
== 1) {
1176 /* Draw the string of each setting on each button. */
1177 int i
= (widget
- WIDGET_NEWSOPT_START_OPTION
) / MOS_WIDG_PER_SETTING
;
1178 DrawString(r
.left
, r
.right
, r
.top
+ 2, this->message_opt
[_news_type_data
[i
].display
], TC_BLACK
, SA_HOR_CENTER
);
1182 virtual void OnInit()
1184 this->dim_message_opt
.width
= 0;
1185 this->dim_message_opt
.height
= 0;
1186 for (const StringID
*str
= message_opt
; *str
!= INVALID_STRING_ID
; str
++) this->dim_message_opt
= maxdim(this->dim_message_opt
, GetStringBoundingBox(*str
));
1189 virtual void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
)
1191 if (widget
>= WIDGET_NEWSOPT_START_OPTION
&& widget
< WIDGET_NEWSOPT_END_OPTION
) {
1192 /* Height is the biggest widget height in a row. */
1193 size
->height
= FONT_HEIGHT_NORMAL
+ max(WD_FRAMERECT_TOP
+ WD_FRAMERECT_BOTTOM
, WD_IMGBTN_TOP
+ WD_IMGBTN_BOTTOM
);
1195 /* Compute width for the label widget only. */
1196 if ((widget
- WIDGET_NEWSOPT_START_OPTION
) % MOS_WIDG_PER_SETTING
== 1) {
1197 size
->width
= this->dim_message_opt
.width
+ padding
.width
+ MOS_BUTTON_SPACE
; // A bit extra for better looks.
1202 /* Size computations for global message options. */
1203 if (widget
== WIDGET_NEWSOPT_DROP_SUMMARY
|| widget
== WIDGET_NEWSOPT_LABEL_SUMMARY
|| widget
== WIDGET_NEWSOPT_SOUNDTICKER
|| widget
== WIDGET_NEWSOPT_SOUNDTICKER_LABEL
) {
1204 /* Height is the biggest widget height in a row. */
1205 size
->height
= FONT_HEIGHT_NORMAL
+ max(WD_FRAMERECT_TOP
+ WD_FRAMERECT_BOTTOM
, WD_DROPDOWNTEXT_TOP
+ WD_DROPDOWNTEXT_BOTTOM
);
1207 if (widget
== WIDGET_NEWSOPT_DROP_SUMMARY
) {
1208 size
->width
= this->dim_message_opt
.width
+ padding
.width
+ MOS_BUTTON_SPACE
; // A bit extra for better looks.
1209 } else if (widget
== WIDGET_NEWSOPT_SOUNDTICKER
) {
1210 size
->width
+= MOS_BUTTON_SPACE
; // A bit extra for better looks.
1217 * Some data on this window has become invalid.
1218 * @param data Information about the changed data.
1219 * @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.
1221 virtual void OnInvalidateData(int data
= 0, bool gui_scope
= true)
1223 if (!gui_scope
) return;
1224 /* Update the dropdown value for 'set all categories'. */
1225 this->GetWidget
<NWidgetCore
>(WIDGET_NEWSOPT_DROP_SUMMARY
)->widget_data
= this->message_opt
[this->state
];
1227 /* Update widget to reflect the value of the #_news_ticker_sound variable. */
1228 this->SetWidgetLoweredState(WIDGET_NEWSOPT_SOUNDTICKER
, _news_ticker_sound
);
1231 virtual void OnClick(Point pt
, int widget
, int click_count
)
1234 case WIDGET_NEWSOPT_DROP_SUMMARY
: // Dropdown menu for all settings
1235 ShowDropDownMenu(this, this->message_opt
, this->state
, WIDGET_NEWSOPT_DROP_SUMMARY
, 0, 0);
1238 case WIDGET_NEWSOPT_SOUNDTICKER
: // Change ticker sound on/off
1239 _news_ticker_sound
^= 1;
1240 this->InvalidateData();
1243 default: { // Clicked on the [<] .. [>] widgets
1244 if (widget
>= WIDGET_NEWSOPT_START_OPTION
&& widget
< WIDGET_NEWSOPT_END_OPTION
) {
1245 int wid
= widget
- WIDGET_NEWSOPT_START_OPTION
;
1246 int element
= wid
/ MOS_WIDG_PER_SETTING
;
1247 byte val
= (_news_type_data
[element
].display
+ ((wid
% MOS_WIDG_PER_SETTING
) ? 1 : -1)) % 3;
1249 this->SetMessageButtonStates(val
, element
);
1250 _news_type_data
[element
].display
= (NewsDisplay
)val
;
1258 virtual void OnDropdownSelect(int widget
, int index
)
1260 this->state
= index
;
1262 for (int i
= 0; i
< NT_END
; i
++) {
1263 this->SetMessageButtonStates(index
, i
);
1264 _news_type_data
[i
].display
= (NewsDisplay
)index
;
1266 this->InvalidateData();
1270 const StringID
MessageOptionsWindow::message_opt
[] = {STR_NEWS_MESSAGES_OFF
, STR_NEWS_MESSAGES_SUMMARY
, STR_NEWS_MESSAGES_FULL
, INVALID_STRING_ID
};
1272 /** Make a column with the buttons for changing each news category setting, and the global settings. */
1273 static NWidgetBase
*MakeButtonsColumn(int *biggest_index
)
1275 NWidgetVertical
*vert_buttons
= new NWidgetVertical
;
1277 /* Top-part of the column, one row for each new category. */
1278 int widnum
= WIDGET_NEWSOPT_START_OPTION
;
1279 for (int i
= 0; i
< NT_END
; i
++) {
1280 NWidgetHorizontal
*hor
= new NWidgetHorizontal
;
1282 NWidgetLeaf
*leaf
= new NWidgetLeaf(WWT_PUSHARROWBTN
, COLOUR_YELLOW
, widnum
, AWV_DECREASE
, STR_TOOLTIP_HSCROLL_BAR_SCROLLS_LIST
);
1283 leaf
->SetFill(1, 1);
1286 leaf
= new NWidgetLeaf(WWT_PUSHTXTBTN
, COLOUR_YELLOW
, widnum
+ 1, STR_EMPTY
, STR_NULL
);
1287 leaf
->SetFill(1, 1);
1290 leaf
= new NWidgetLeaf(WWT_PUSHARROWBTN
, COLOUR_YELLOW
, widnum
+ 2, AWV_INCREASE
, STR_TOOLTIP_HSCROLL_BAR_SCROLLS_LIST
);
1291 leaf
->SetFill(1, 1);
1293 vert_buttons
->Add(hor
);
1295 widnum
+= MOS_WIDG_PER_SETTING
;
1297 *biggest_index
= widnum
- MOS_WIDG_PER_SETTING
+ 2;
1299 /* Space between the category buttons and the global settings buttons. */
1300 NWidgetSpacer
*spacer
= new NWidgetSpacer(0, MOS_ABOVE_GLOBAL_SETTINGS
);
1301 vert_buttons
->Add(spacer
);
1303 /* Bottom part of the column with buttons for global changes. */
1304 NWidgetLeaf
*leaf
= new NWidgetLeaf(WWT_DROPDOWN
, COLOUR_YELLOW
, WIDGET_NEWSOPT_DROP_SUMMARY
, STR_EMPTY
, STR_NULL
);
1305 leaf
->SetFill(1, 1);
1306 vert_buttons
->Add(leaf
);
1308 leaf
= new NWidgetLeaf(WWT_TEXTBTN_2
, COLOUR_YELLOW
, WIDGET_NEWSOPT_SOUNDTICKER
, STR_STATION_BUILD_COVERAGE_OFF
, STR_NULL
);
1309 leaf
->SetFill(1, 1);
1310 vert_buttons
->Add(leaf
);
1312 *biggest_index
= max(*biggest_index
, max
<int>(WIDGET_NEWSOPT_DROP_SUMMARY
, WIDGET_NEWSOPT_SOUNDTICKER
));
1313 return vert_buttons
;
1316 /** Make a column with descriptions for each news category and the global settings. */
1317 static NWidgetBase
*MakeDescriptionColumn(int *biggest_index
)
1319 NWidgetVertical
*vert_desc
= new NWidgetVertical
;
1321 /* Top-part of the column, one row for each new category. */
1322 int widnum
= WIDGET_NEWSOPT_START_OPTION
;
1323 for (int i
= 0; i
< NT_END
; i
++) {
1324 NWidgetHorizontal
*hor
= new NWidgetHorizontal
;
1326 /* Descriptive text. */
1327 NWidgetLeaf
*leaf
= new NWidgetLeaf(WWT_TEXT
, COLOUR_YELLOW
, widnum
+ 3, _news_type_data
[i
].description
, STR_NULL
);
1329 /* Filling empty space to push text to the left. */
1330 NWidgetSpacer
*spacer
= new NWidgetSpacer(0, 0);
1331 spacer
->SetFill(1, 0);
1333 vert_desc
->Add(hor
);
1335 widnum
+= MOS_WIDG_PER_SETTING
;
1337 *biggest_index
= widnum
- MOS_WIDG_PER_SETTING
+ 3;
1339 /* Space between the category descriptions and the global settings descriptions. */
1340 NWidgetSpacer
*spacer
= new NWidgetSpacer(0, MOS_ABOVE_GLOBAL_SETTINGS
);
1341 vert_desc
->Add(spacer
);
1343 /* Bottom part of the column with descriptions of global changes. */
1344 NWidgetHorizontal
*hor
= new NWidgetHorizontal
;
1345 NWidgetLeaf
*leaf
= new NWidgetLeaf(WWT_TEXT
, COLOUR_YELLOW
, WIDGET_NEWSOPT_LABEL_SUMMARY
, STR_NEWS_MESSAGES_ALL
, STR_NULL
);
1347 /* Filling empty space to push text to the left. */
1348 spacer
= new NWidgetSpacer(0, 0);
1349 spacer
->SetFill(1, 0);
1351 vert_desc
->Add(hor
);
1353 hor
= new NWidgetHorizontal
;
1354 leaf
= new NWidgetLeaf(WWT_TEXT
, COLOUR_YELLOW
, WIDGET_NEWSOPT_SOUNDTICKER_LABEL
, STR_NEWS_MESSAGES_SOUND
, STR_NULL
);
1356 /* Filling empty space to push text to the left. */
1357 spacer
= new NWidgetSpacer(0, 0);
1358 leaf
->SetFill(1, 0);
1360 vert_desc
->Add(hor
);
1362 *biggest_index
= max(*biggest_index
, max
<int>(WIDGET_NEWSOPT_LABEL_SUMMARY
, WIDGET_NEWSOPT_SOUNDTICKER_LABEL
));
1366 static const NWidgetPart _nested_message_options_widgets
[] = {
1367 NWidget(NWID_HORIZONTAL
),
1368 NWidget(WWT_CLOSEBOX
, COLOUR_BROWN
),
1369 NWidget(WWT_CAPTION
, COLOUR_BROWN
), SetDataTip(STR_NEWS_MESSAGE_OPTIONS_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
1371 NWidget(WWT_PANEL
, COLOUR_BROWN
, WIDGET_NEWSOPT_BACKGROUND
),
1372 NWidget(NWID_HORIZONTAL
),
1373 NWidget(NWID_SPACER
), SetFill(1, 0),
1374 NWidget(WWT_LABEL
, COLOUR_BROWN
, WIDGET_NEWSOPT_LABEL
), SetMinimalSize(0, 14), SetDataTip(STR_NEWS_MESSAGE_TYPES
, STR_NULL
),
1375 NWidget(NWID_SPACER
), SetFill(1, 0),
1377 NWidget(NWID_HORIZONTAL
),
1378 NWidget(NWID_SPACER
), SetMinimalSize(MOS_LEFT_EDGE
, 0),
1379 NWidgetFunction(MakeButtonsColumn
),
1380 NWidget(NWID_SPACER
), SetMinimalSize(MOS_COLUMN_SPACING
, 0),
1381 NWidgetFunction(MakeDescriptionColumn
),
1382 NWidget(NWID_SPACER
), SetMinimalSize(MOS_RIGHT_EDGE
, 0),
1384 NWidget(NWID_SPACER
), SetMinimalSize(0, MOS_BOTTOM_EDGE
),
1388 static const WindowDesc
_message_options_desc(
1390 WC_GAME_OPTIONS
, WC_NONE
,
1391 WDF_UNCLICK_BUTTONS
,
1392 _nested_message_options_widgets
, lengthof(_nested_message_options_widgets
)
1396 * Show the settings window for news messages.
1398 void ShowMessageOptions()
1400 DeleteWindowById(WC_GAME_OPTIONS
, 0);
1401 new MessageOptionsWindow(&_message_options_desc
);