src/news_gui.cpp
branchnoai
changeset 10776 07203fc29812
parent 10715 6bdf79ffb022
child 10829 8a0ec0f0f928
equal deleted inserted replaced
10774:2c882f0468f2 10776:07203fc29812
    22 #include "player_face.h"
    22 #include "player_face.h"
    23 
    23 
    24 #include "table/sprites.h"
    24 #include "table/sprites.h"
    25 #include "table/strings.h"
    25 #include "table/strings.h"
    26 
    26 
    27 /** @file news_gui.cpp
       
    28  *
       
    29  * News system is realized as a FIFO queue (in an array)
       
    30  * The positions in the queue can't be rearranged, we only access
       
    31  * the array elements through pointers to the elements. Once the
       
    32  * array is full, the oldest entry (\a _oldest_news) is being overwritten
       
    33  * by the newest (\a _latest_news).
       
    34  *
       
    35  * \verbatim
       
    36  * oldest                   current   lastest
       
    37  *  |                          |         |
       
    38  * [O------------F-------------C---------L           ]
       
    39  *               |
       
    40  *            forced
       
    41  * \endverbatim
       
    42  *
       
    43  * Of course by using an array we can have situations like
       
    44  *
       
    45  * \verbatim
       
    46  * [----L          O-----F---------C-----------------]
       
    47  * This is where we have wrapped around the array and have
       
    48  * (MAX_NEWS - O) + L news items
       
    49  * \endverbatim
       
    50  */
       
    51 
       
    52 #define NB_WIDG_PER_SETTING 4
    27 #define NB_WIDG_PER_SETTING 4
    53 
       
    54 typedef byte NewsID;
       
    55 #define INVALID_NEWS 255
       
    56 
    28 
    57 NewsItem _statusbar_news_item;
    29 NewsItem _statusbar_news_item;
    58 bool _news_ticker_sound;
    30 bool _news_ticker_sound;
    59 static NewsItem *_news_items = NULL;        ///< The news FIFO queue
    31 
    60 static uint _max_news_items = 0;            ///< size of news FIFO queue
    32 static uint MIN_NEWS_AMOUNT = 30;           ///< prefered minimum amount of news messages
    61 static NewsID _current_news = INVALID_NEWS; ///< points to news item that should be shown next
    33 static uint _total_news = 0;                ///< current number of news items
    62 static NewsID _oldest_news = 0;             ///< points to first item in fifo queue
    34 static NewsItem *_oldest_news = NULL;       ///< head of news items queue
    63 static NewsID _latest_news = INVALID_NEWS;  ///< points to last item in fifo queue
    35 static NewsItem *_latest_news = NULL;       ///< tail of news items queue
    64 
    36 
    65 /** Forced news item.
    37 /** Forced news item.
    66  * Users can force an item by accessing the history or "last message".
    38  * Users can force an item by accessing the history or "last message".
    67  * If the message being shown was forced by the user, its index is stored in
    39  * If the message being shown was forced by the user, a pointer is stored
    68  * _forced_news. Otherwise, \a _forced_news variable is INVALID_NEWS. */
    40  * in _forced_news. Otherwise, \a _forced_news variable is NULL. */
    69 static NewsID _forced_news = INVALID_NEWS;
    41 static NewsItem *_forced_news = NULL;       ///< item the user has asked for
    70 
    42 
    71 static uint _total_news = 0; ///< Number of news items in FIFO queue @see _news_items
    43 /** Current news item (last item shown regularly). */
    72 static void MoveToNextItem();
    44 static NewsItem *_current_news = NULL;
    73 
    45 
    74 
    46 
    75 typedef void DrawNewsCallbackProc(struct Window *w, const NewsItem *ni);
    47 typedef void DrawNewsCallbackProc(struct Window *w, const NewsItem *ni);
    76 void DrawNewsNewVehicleAvail(Window *w, const NewsItem *ni);
    48 void DrawNewsNewVehicleAvail(Window *w, const NewsItem *ni);
    77 
    49 
   170 	{ NT_ACCEPTANCE,      NM_SMALL,    NF_VIEWPORT|NF_TILE,    NULL                    }, ///< NS_ACCEPTANCE
   142 	{ NT_ACCEPTANCE,      NM_SMALL,    NF_VIEWPORT|NF_TILE,    NULL                    }, ///< NS_ACCEPTANCE
   171 	{ NT_SUBSIDIES,       NM_NORMAL,   NF_TILE|NF_TILE2,       NULL                    }, ///< NS_SUBSIDIES
   143 	{ NT_SUBSIDIES,       NM_NORMAL,   NF_TILE|NF_TILE2,       NULL                    }, ///< NS_SUBSIDIES
   172 	{ NT_GENERAL,         NM_NORMAL,   NF_TILE,                NULL                    }, ///< NS_GENERAL
   144 	{ NT_GENERAL,         NM_NORMAL,   NF_TILE,                NULL                    }, ///< NS_GENERAL
   173 };
   145 };
   174 
   146 
   175 /** Initialize the news-items data structures */
       
   176 void InitNewsItemStructs()
       
   177 {
       
   178 	free(_news_items);
       
   179 	_max_news_items = max(ScaleByMapSize(30), 30U);
       
   180 	_news_items = CallocT<NewsItem>(_max_news_items);
       
   181 	_current_news = INVALID_NEWS;
       
   182 	_oldest_news = 0;
       
   183 	_latest_news = INVALID_NEWS;
       
   184 	_forced_news = INVALID_NEWS;
       
   185 	_total_news = 0;
       
   186 }
       
   187 
       
   188 struct NewsWindow : Window {
       
   189 	uint16 chat_height;
       
   190 	NewsItem *ni;
       
   191 
       
   192 	NewsWindow(const WindowDesc *desc, NewsItem *ni) : Window(desc), ni(ni)
       
   193 	{
       
   194 		const Window *w = FindWindowById(WC_SEND_NETWORK_MSG, 0);
       
   195 		this->chat_height = (w != NULL) ? w->height : 0;
       
   196 
       
   197 		this->ni = &_news_items[_forced_news == INVALID_NEWS ? _current_news : _forced_news];
       
   198 		this->flags4 |= WF_DISABLE_VP_SCROLL;
       
   199 
       
   200 		this->FindWindowPlacementAndResize(desc);
       
   201 	}
       
   202 
       
   203 	void DrawNewsBorder()
       
   204 	{
       
   205 		int left = 0;
       
   206 		int right = this->width - 1;
       
   207 		int top = 0;
       
   208 		int bottom = this->height - 1;
       
   209 
       
   210 		GfxFillRect(left, top, right, bottom, 0xF);
       
   211 
       
   212 		GfxFillRect(left, top, left, bottom, 0xD7);
       
   213 		GfxFillRect(right, top, right, bottom, 0xD7);
       
   214 		GfxFillRect(left, top, right, top, 0xD7);
       
   215 		GfxFillRect(left, bottom, right, bottom, 0xD7);
       
   216 
       
   217 		DrawString(left + 2, top + 1, STR_00C6, TC_FROMSTRING);
       
   218 	}
       
   219 
       
   220 	virtual void OnPaint()
       
   221 	{
       
   222 		const NewsMode display_mode = _news_subtype_data[this->ni->subtype].display_mode;
       
   223 
       
   224 		switch (display_mode) {
       
   225 			case NM_NORMAL:
       
   226 			case NM_THIN: {
       
   227 				this->DrawNewsBorder();
       
   228 
       
   229 				DrawString(2, 1, STR_00C6, TC_FROMSTRING);
       
   230 
       
   231 				SetDParam(0, this->ni->date);
       
   232 				DrawStringRightAligned(428, 1, STR_01FF, TC_FROMSTRING);
       
   233 
       
   234 				if (!(this->ni->flags & NF_VIEWPORT)) {
       
   235 					CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
       
   236 					DrawStringMultiCenter(215, display_mode == NM_NORMAL ? 76 : 56,
       
   237 						this->ni->string_id, this->width - 4);
       
   238 				} else {
       
   239 					/* Back up transparency options to draw news view */
       
   240 					TransparencyOptionBits to_backup = _transparency_opt;
       
   241 					_transparency_opt = 0;
       
   242 					this->DrawViewport();
       
   243 					_transparency_opt = to_backup;
       
   244 
       
   245 					/* Shade the viewport into gray, or color*/
       
   246 					ViewPort *vp = this->viewport;
       
   247 					GfxFillRect(vp->left - this->left, vp->top - this->top,
       
   248 						vp->left - this->left + vp->width - 1, vp->top - this->top + vp->height - 1,
       
   249 						(this->ni->flags & NF_INCOLOR ? PALETTE_TO_TRANSPARENT : PALETTE_TO_STRUCT_GREY) | (1 << USE_COLORTABLE)
       
   250 					);
       
   251 
       
   252 					CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
       
   253 					DrawStringMultiCenter(this->width / 2, 20, this->ni->string_id, this->width - 4);
       
   254 				}
       
   255 				break;
       
   256 			}
       
   257 
       
   258 			case NM_CALLBACK:
       
   259 				this->DrawNewsBorder();
       
   260 				(_news_subtype_data[this->ni->subtype].callback)(this, ni);
       
   261 				break;
       
   262 
       
   263 			default:
       
   264 				this->DrawWidgets();
       
   265 				if (!(this->ni->flags & NF_VIEWPORT)) {
       
   266 					CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
       
   267 					DrawStringMultiCenter(140, 38, this->ni->string_id, 276);
       
   268 				} else {
       
   269 					this->DrawViewport();
       
   270 					CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
       
   271 					DrawStringMultiCenter(this->width / 2, this->height - 16, this->ni->string_id, this->width - 4);
       
   272 				}
       
   273 				break;
       
   274 		}
       
   275 	}
       
   276 
       
   277 	virtual void OnClick(Point pt, int widget)
       
   278 	{
       
   279 		switch (widget) {
       
   280 			case 1:
       
   281 				this->ni->duration = 0;
       
   282 				delete this;
       
   283 				_forced_news = INVALID_NEWS;
       
   284 				break;
       
   285 
       
   286 			case 0:
       
   287 				if (this->ni->flags & NF_VEHICLE) {
       
   288 					Vehicle *v = GetVehicle(this->ni->data_a);
       
   289 					ScrollMainWindowTo(v->x_pos, v->y_pos);
       
   290 				} else if (this->ni->flags & NF_TILE) {
       
   291 					if (_ctrl_pressed) {
       
   292 						ShowExtraViewPortWindow(this->ni->data_a);
       
   293 						if (this->ni->flags & NF_TILE2) {
       
   294 							ShowExtraViewPortWindow(this->ni->data_b);
       
   295 						}
       
   296 					} else {
       
   297 						if (!ScrollMainWindowToTile(this->ni->data_a) && this->ni->flags & NF_TILE2) {
       
   298 							ScrollMainWindowToTile(this->ni->data_b);
       
   299 						}
       
   300 					}
       
   301 				}
       
   302 				break;
       
   303 		}
       
   304 	}
       
   305 
       
   306 	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
       
   307 	{
       
   308 		if (keycode == WKC_SPACE) {
       
   309 			/* Don't continue. */
       
   310 			delete this;
       
   311 			return ES_HANDLED;
       
   312 		}
       
   313 		return ES_NOT_HANDLED;
       
   314 	}
       
   315 
       
   316 	virtual void OnInvalidateData(int data)
       
   317 	{
       
   318 		/* The chatbar has notified us that is was either created or closed */
       
   319 		this->chat_height = data;
       
   320 	}
       
   321 
       
   322 	virtual void OnTick()
       
   323 	{
       
   324 		/* Scroll up newsmessages from the bottom in steps of 4 pixels */
       
   325 		int y = max(this->top - 4, _screen.height - this->height - 12 - this->chat_height);
       
   326 		if (y == this->top) return;
       
   327 
       
   328 		if (this->viewport != NULL) this->viewport->top += y - this->top;
       
   329 
       
   330 		int diff = Delta(this->top, y);
       
   331 		this->top = y;
       
   332 
       
   333 		SetDirtyBlocks(this->left, this->top - diff, this->left + this->width, this->top + this->height);
       
   334 	}
       
   335 };
       
   336 
       
   337 /**
       
   338  * Return the correct index in the pseudo-fifo
       
   339  * queue and deals with overflows when increasing the index
       
   340  */
       
   341 static inline NewsID IncreaseIndex(NewsID i)
       
   342 {
       
   343 	assert(i != INVALID_NEWS);
       
   344 	return (i + 1) % _max_news_items;
       
   345 }
       
   346 
       
   347 /**
       
   348  * Return the correct index in the pseudo-fifo
       
   349  * queue and deals with overflows when decreasing the index
       
   350  */
       
   351 static inline NewsID DecreaseIndex(NewsID i)
       
   352 {
       
   353 	assert(i != INVALID_NEWS);
       
   354 	return (i + _max_news_items - 1) % _max_news_items;
       
   355 }
       
   356 
       
   357 /**
       
   358  * Add a new newsitem to be shown.
       
   359  * @param string String to display
       
   360  * @param subtype news category, any of the NewsSubtype enums (NS_)
       
   361  * @param data_a news-specific value based on news type
       
   362  * @param data_b news-specific value based on news type
       
   363  *
       
   364  * @see NewsSubype
       
   365  */
       
   366 void AddNewsItem(StringID string, NewsSubtype subtype, uint data_a, uint data_b)
       
   367 {
       
   368 	if (_game_mode == GM_MENU) return;
       
   369 
       
   370 	/* check the rare case that the oldest (to be overwritten) news item is open */
       
   371 	if (_total_news == _max_news_items && (_oldest_news == _current_news || _oldest_news == _forced_news)) {
       
   372 		MoveToNextItem();
       
   373 	}
       
   374 
       
   375 	if (_total_news < _max_news_items) _total_news++;
       
   376 
       
   377 	/* Increase _latest_news. If we have no news yet, use _oldest news as an
       
   378 	 * index. We cannot use 0 as _oldest_news can jump around due to
       
   379 	 * DeleteVehicleNews */
       
   380 	NewsID l_news = _latest_news;
       
   381 	_latest_news = (_latest_news == INVALID_NEWS) ? _oldest_news : IncreaseIndex(_latest_news);
       
   382 
       
   383 	/* If the fifo-buffer is full, overwrite the oldest entry */
       
   384 	if (l_news != INVALID_NEWS && _latest_news == _oldest_news) {
       
   385 		assert(_total_news == _max_news_items);
       
   386 		_oldest_news = IncreaseIndex(_oldest_news);
       
   387 	}
       
   388 
       
   389 	/*DEBUG(misc, 0, "+cur %3d, old %2d, lat %3d, for %3d, tot %2d",
       
   390 	  _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/
       
   391 
       
   392 	/* Add news to _latest_news */
       
   393 	NewsItem *ni = &_news_items[_latest_news];
       
   394 	memset(ni, 0, sizeof(*ni));
       
   395 
       
   396 	ni->string_id = string;
       
   397 	ni->subtype = subtype;
       
   398 	ni->flags = _news_subtype_data[subtype].flags;
       
   399 
       
   400 	/* show this news message in color? */
       
   401 	if (_cur_year >= _settings.gui.colored_news_year) ni->flags |= NF_INCOLOR;
       
   402 
       
   403 	ni->data_a = data_a;
       
   404 	ni->data_b = data_b;
       
   405 	ni->date = _date;
       
   406 	CopyOutDParam(ni->params, 0, lengthof(ni->params));
       
   407 
       
   408 	Window *w = FindWindowById(WC_MESSAGE_HISTORY, 0);
       
   409 	if (w == NULL) return;
       
   410 	w->SetDirty();
       
   411 	w->vscroll.count = _total_news;
       
   412 }
       
   413 
       
   414 
       
   415 /**
   147 /**
   416  * Per-NewsType data
   148  * Per-NewsType data
   417  */
   149  */
   418 NewsTypeData _news_type_data[NT_END] = {
   150 NewsTypeData _news_type_data[NT_END] = {
   419 	/* name,              age, sound,           display */
   151 	/* name,              age, sound,           display */
   431 	{ "acceptance",        90, SND_BEGIN,       ND_FULL },  ///< NT_ACCEPTANCE
   163 	{ "acceptance",        90, SND_BEGIN,       ND_FULL },  ///< NT_ACCEPTANCE
   432 	{ "subsidies",        180, SND_BEGIN,       ND_FULL },  ///< NT_SUBSIDIES
   164 	{ "subsidies",        180, SND_BEGIN,       ND_FULL },  ///< NT_SUBSIDIES
   433 	{ "general",           60, SND_BEGIN,       ND_FULL },  ///< NT_GENERAL
   165 	{ "general",           60, SND_BEGIN,       ND_FULL },  ///< NT_GENERAL
   434 };
   166 };
   435 
   167 
       
   168 struct NewsWindow : Window {
       
   169 	uint16 chat_height;
       
   170 	NewsItem *ni;
       
   171 
       
   172 	NewsWindow(const WindowDesc *desc, NewsItem *ni) : Window(desc), ni(ni)
       
   173 	{
       
   174 		const Window *w = FindWindowById(WC_SEND_NETWORK_MSG, 0);
       
   175 		this->chat_height = (w != NULL) ? w->height : 0;
       
   176 
       
   177 		this->ni = _forced_news == NULL ? _current_news : _forced_news;
       
   178 		this->flags4 |= WF_DISABLE_VP_SCROLL;
       
   179 
       
   180 		this->FindWindowPlacementAndResize(desc);
       
   181 	}
       
   182 
       
   183 	void DrawNewsBorder()
       
   184 	{
       
   185 		int left = 0;
       
   186 		int right = this->width - 1;
       
   187 		int top = 0;
       
   188 		int bottom = this->height - 1;
       
   189 
       
   190 		GfxFillRect(left, top, right, bottom, 0xF);
       
   191 
       
   192 		GfxFillRect(left, top, left, bottom, 0xD7);
       
   193 		GfxFillRect(right, top, right, bottom, 0xD7);
       
   194 		GfxFillRect(left, top, right, top, 0xD7);
       
   195 		GfxFillRect(left, bottom, right, bottom, 0xD7);
       
   196 
       
   197 		DrawString(left + 2, top + 1, STR_00C6, TC_FROMSTRING);
       
   198 	}
       
   199 
       
   200 	virtual void OnPaint()
       
   201 	{
       
   202 		const NewsMode display_mode = _news_subtype_data[this->ni->subtype].display_mode;
       
   203 
       
   204 		switch (display_mode) {
       
   205 			case NM_NORMAL:
       
   206 			case NM_THIN: {
       
   207 				this->DrawNewsBorder();
       
   208 
       
   209 				DrawString(2, 1, STR_00C6, TC_FROMSTRING);
       
   210 
       
   211 				SetDParam(0, this->ni->date);
       
   212 				DrawStringRightAligned(428, 1, STR_01FF, TC_FROMSTRING);
       
   213 
       
   214 				if (!(this->ni->flags & NF_VIEWPORT)) {
       
   215 					CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
       
   216 					DrawStringMultiCenter(215, display_mode == NM_NORMAL ? 76 : 56,
       
   217 						this->ni->string_id, this->width - 4);
       
   218 				} else {
       
   219 					/* Back up transparency options to draw news view */
       
   220 					TransparencyOptionBits to_backup = _transparency_opt;
       
   221 					_transparency_opt = 0;
       
   222 					this->DrawViewport();
       
   223 					_transparency_opt = to_backup;
       
   224 
       
   225 					/* Shade the viewport into gray, or color*/
       
   226 					ViewPort *vp = this->viewport;
       
   227 					GfxFillRect(vp->left - this->left, vp->top - this->top,
       
   228 						vp->left - this->left + vp->width - 1, vp->top - this->top + vp->height - 1,
       
   229 						(this->ni->flags & NF_INCOLOR ? PALETTE_TO_TRANSPARENT : PALETTE_TO_STRUCT_GREY) | (1 << USE_COLORTABLE)
       
   230 					);
       
   231 
       
   232 					CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
       
   233 					DrawStringMultiCenter(this->width / 2, 20, this->ni->string_id, this->width - 4);
       
   234 				}
       
   235 				break;
       
   236 			}
       
   237 
       
   238 			case NM_CALLBACK:
       
   239 				this->DrawNewsBorder();
       
   240 				(_news_subtype_data[this->ni->subtype].callback)(this, ni);
       
   241 				break;
       
   242 
       
   243 			default:
       
   244 				this->DrawWidgets();
       
   245 				if (!(this->ni->flags & NF_VIEWPORT)) {
       
   246 					CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
       
   247 					DrawStringMultiCenter(140, 38, this->ni->string_id, 276);
       
   248 				} else {
       
   249 					this->DrawViewport();
       
   250 					CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
       
   251 					DrawStringMultiCenter(this->width / 2, this->height - 16, this->ni->string_id, this->width - 4);
       
   252 				}
       
   253 				break;
       
   254 		}
       
   255 	}
       
   256 
       
   257 	virtual void OnClick(Point pt, int widget)
       
   258 	{
       
   259 		switch (widget) {
       
   260 			case 1:
       
   261 				this->ni->duration = 0;
       
   262 				delete this;
       
   263 				_forced_news = NULL;
       
   264 				break;
       
   265 
       
   266 			case 0:
       
   267 				if (this->ni->flags & NF_VEHICLE) {
       
   268 					Vehicle *v = GetVehicle(this->ni->data_a);
       
   269 					ScrollMainWindowTo(v->x_pos, v->y_pos);
       
   270 				} else if (this->ni->flags & NF_TILE) {
       
   271 					if (_ctrl_pressed) {
       
   272 						ShowExtraViewPortWindow(this->ni->data_a);
       
   273 						if (this->ni->flags & NF_TILE2) {
       
   274 							ShowExtraViewPortWindow(this->ni->data_b);
       
   275 						}
       
   276 					} else {
       
   277 						if (!ScrollMainWindowToTile(this->ni->data_a) && this->ni->flags & NF_TILE2) {
       
   278 							ScrollMainWindowToTile(this->ni->data_b);
       
   279 						}
       
   280 					}
       
   281 				}
       
   282 				break;
       
   283 		}
       
   284 	}
       
   285 
       
   286 	virtual EventState OnKeyPress(uint16 key, uint16 keycode)
       
   287 	{
       
   288 		if (keycode == WKC_SPACE) {
       
   289 			/* Don't continue. */
       
   290 			delete this;
       
   291 			return ES_HANDLED;
       
   292 		}
       
   293 		return ES_NOT_HANDLED;
       
   294 	}
       
   295 
       
   296 	virtual void OnInvalidateData(int data)
       
   297 	{
       
   298 		/* The chatbar has notified us that is was either created or closed */
       
   299 		this->chat_height = data;
       
   300 	}
       
   301 
       
   302 	virtual void OnTick()
       
   303 	{
       
   304 		/* Scroll up newsmessages from the bottom in steps of 4 pixels */
       
   305 		int y = max(this->top - 4, _screen.height - this->height - 12 - this->chat_height);
       
   306 		if (y == this->top) return;
       
   307 
       
   308 		if (this->viewport != NULL) this->viewport->top += y - this->top;
       
   309 
       
   310 		int diff = Delta(this->top, y);
       
   311 		this->top = y;
       
   312 
       
   313 		SetDirtyBlocks(this->left, this->top - diff, this->left + this->width, this->top + this->height);
       
   314 	}
       
   315 };
       
   316 
   436 
   317 
   437 static const Widget _news_type13_widgets[] = {
   318 static const Widget _news_type13_widgets[] = {
   438 {      WWT_PANEL,   RESIZE_NONE,    15,     0,   429,     0,   169, 0x0, STR_NULL},
   319 {      WWT_PANEL,   RESIZE_NONE,    15,     0,   429,     0,   169, 0x0, STR_NULL},
   439 {      WWT_PANEL,   RESIZE_NONE,    15,     0,    10,     0,    11, 0x0, STR_NULL},
   320 {      WWT_PANEL,   RESIZE_NONE,    15,     0,    10,     0,    11, 0x0, STR_NULL},
   440 {   WIDGETS_END},
   321 {   WIDGETS_END},
   514 				InitializeWindowViewport(w, 3, 17, 274, 47,
   395 				InitializeWindowViewport(w, 3, 17, 274, 47,
   515 					ni->data_a | (ni->flags & NF_VEHICLE ? 0x80000000 : 0), ZOOM_LVL_NEWS);
   396 					ni->data_a | (ni->flags & NF_VEHICLE ? 0x80000000 : 0), ZOOM_LVL_NEWS);
   516 			}
   397 			}
   517 			break;
   398 			break;
   518 	}
   399 	}
   519 
       
   520 	/*DEBUG(misc, 0, " cur %3d, old %2d, lat %3d, for %3d, tot %2d",
       
   521 	  _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/
       
   522 }
   400 }
   523 
   401 
   524 /** Show news item in the ticker */
   402 /** Show news item in the ticker */
   525 static void ShowTicker(const NewsItem *ni)
   403 static void ShowTicker(const NewsItem *ni)
   526 {
   404 {
   528 
   406 
   529 	_statusbar_news_item = *ni;
   407 	_statusbar_news_item = *ni;
   530 	InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_TICKER);
   408 	InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_TICKER);
   531 }
   409 }
   532 
   410 
       
   411 /** Initialize the news-items data structures */
       
   412 void InitNewsItemStructs()
       
   413 {
       
   414 	for (NewsItem *ni = _oldest_news; ni != NULL; ) {
       
   415 		NewsItem *next = ni->next;
       
   416 		delete ni;
       
   417 		ni = next;
       
   418 	}
       
   419 
       
   420 	_total_news = 0;
       
   421 	_oldest_news = NULL;
       
   422 	_latest_news = NULL;
       
   423 	_forced_news = NULL;
       
   424 	_current_news = NULL;
       
   425 }
   533 
   426 
   534 /**
   427 /**
   535  * Are we ready to show another news item?
   428  * Are we ready to show another news item?
   536  * Only if nothing is in the newsticker and no newspaper is displayed
   429  * Only if nothing is in the newsticker and no newspaper is displayed
   537  */
   430  */
   538 static bool ReadyForNextItem()
   431 static bool ReadyForNextItem()
   539 {
   432 {
   540 	NewsID item = (_forced_news == INVALID_NEWS) ? _current_news : _forced_news;
   433 	NewsItem *ni = _forced_news == NULL ? _current_news : _forced_news;
   541 
   434 	if (ni == NULL) return true;
   542 	if (item >= _max_news_items) return true;
       
   543 	NewsItem *ni = &_news_items[item];
       
   544 
   435 
   545 	/* Ticker message
   436 	/* Ticker message
   546 	 * Check if the status bar message is still being displayed? */
   437 	 * Check if the status bar message is still being displayed? */
   547 	if (IsNewsTickerShown()) return false;
   438 	if (IsNewsTickerShown()) return false;
   548 
   439 
   555 
   446 
   556 /** Move to the next news item */
   447 /** Move to the next news item */
   557 static void MoveToNextItem()
   448 static void MoveToNextItem()
   558 {
   449 {
   559 	DeleteWindowById(WC_NEWS_WINDOW, 0);
   450 	DeleteWindowById(WC_NEWS_WINDOW, 0);
   560 	_forced_news = INVALID_NEWS;
   451 	_forced_news = NULL;
   561 
   452 
   562 	/* if we're not at the last item, then move on */
   453 	/* if we're not at the last item, then move on */
   563 	if (_current_news != _latest_news) {
   454 	if (_current_news != _latest_news) {
   564 		_current_news = (_current_news == INVALID_NEWS) ? _oldest_news : IncreaseIndex(_current_news);
   455 		_current_news = (_current_news == NULL) ? _oldest_news : _current_news->next;
   565 		NewsItem *ni = &_news_items[_current_news];
   456 		NewsItem *ni = _current_news;
   566 		const NewsType type = _news_subtype_data[ni->subtype].type;
   457 		const NewsType type = _news_subtype_data[ni->subtype].type;
   567 
   458 
   568 		/* check the date, don't show too old items */
   459 		/* check the date, don't show too old items */
   569 		if (_date - _news_type_data[type].age > ni->date) return;
   460 		if (_date - _news_type_data[type].age > ni->date) return;
   570 
   461 
   586 				break;
   477 				break;
   587 		}
   478 		}
   588 	}
   479 	}
   589 }
   480 }
   590 
   481 
       
   482 /**
       
   483  * Add a new newsitem to be shown.
       
   484  * @param string String to display
       
   485  * @param subtype news category, any of the NewsSubtype enums (NS_)
       
   486  * @param data_a news-specific value based on news type
       
   487  * @param data_b news-specific value based on news type
       
   488  *
       
   489  * @see NewsSubype
       
   490  */
       
   491 void AddNewsItem(StringID string, NewsSubtype subtype, uint data_a, uint data_b)
       
   492 {
       
   493 	if (_game_mode == GM_MENU) return;
       
   494 
       
   495 	/* Create new news item node */
       
   496 	NewsItem *ni = new NewsItem;
       
   497 
       
   498 	ni->string_id = string;
       
   499 	ni->subtype = subtype;
       
   500 	ni->flags = _news_subtype_data[subtype].flags;
       
   501 
       
   502 	/* show this news message in color? */
       
   503 	if (_cur_year >= _settings_client.gui.colored_news_year) ni->flags |= NF_INCOLOR;
       
   504 
       
   505 	ni->data_a = data_a;
       
   506 	ni->data_b = data_b;
       
   507 	ni->date = _date;
       
   508 	CopyOutDParam(ni->params, 0, lengthof(ni->params));
       
   509 
       
   510 	if (_total_news++ == 0) {
       
   511 		assert(_oldest_news == NULL);
       
   512 		_oldest_news = ni;
       
   513 		ni->prev = NULL;
       
   514 	} else {
       
   515 		assert(_latest_news->next == NULL);
       
   516 		_latest_news->next = ni;
       
   517 		ni->prev = _latest_news;
       
   518 	}
       
   519 
       
   520 	ni->next = NULL;
       
   521 	_latest_news = ni;
       
   522 
       
   523 	InvalidateWindow(WC_MESSAGE_HISTORY, 0);
       
   524 }
       
   525 
       
   526 /** Delete a news item from the queue */
       
   527 static void DeleteNewsItem(NewsItem *ni)
       
   528 {
       
   529 	if (_forced_news == ni) {
       
   530 		/* about to remove the currently forced item; skip to next */
       
   531 		MoveToNextItem();
       
   532 	}
       
   533 
       
   534 	if ((_current_news == ni) && (FindWindowById(WC_MESSAGE_HISTORY, 0) != NULL)) {
       
   535 		/* about to remove the currently displayed item; also skip */
       
   536 		MoveToNextItem();
       
   537 	}
       
   538 
       
   539 	/* delete item */
       
   540 
       
   541 	if (ni->prev != NULL) {
       
   542 		ni->prev->next = ni->next;
       
   543 	} else {
       
   544 		assert(_oldest_news == ni);
       
   545 		_oldest_news = ni->next;
       
   546 	}
       
   547 
       
   548 	if (ni->next != NULL) {
       
   549 		ni->next->prev = ni->prev;
       
   550 	} else {
       
   551 		assert(_latest_news == ni);
       
   552 		_latest_news = ni->prev;
       
   553 	}
       
   554 
       
   555 	if (_current_news == ni) _current_news = ni->prev;
       
   556 	_total_news--;
       
   557 	delete ni;
       
   558 
       
   559 	InvalidateWindow(WC_MESSAGE_HISTORY, 0);
       
   560 }
       
   561 
       
   562 void DeleteVehicleNews(VehicleID vid, StringID news)
       
   563 {
       
   564 	NewsItem *ni = _oldest_news;
       
   565 
       
   566 	while (ni != NULL) {
       
   567 		if (ni->flags & NF_VEHICLE &&
       
   568 				ni->data_a == vid &&
       
   569 				(news == INVALID_STRING_ID || ni->string_id == news)) {
       
   570 			/* grab a pointer to the next item before ni is freed */
       
   571 			NewsItem *p = ni->next;
       
   572 			DeleteNewsItem(ni);
       
   573 			ni = p;
       
   574 		} else {
       
   575 			ni = ni->next;
       
   576 		}
       
   577 	}
       
   578 }
       
   579 
       
   580 void RemoveOldNewsItems()
       
   581 {
       
   582 	NewsItem *next;
       
   583 	for (NewsItem *cur = _oldest_news; _total_news > MIN_NEWS_AMOUNT && cur != NULL; cur = next) {
       
   584 		next = cur->next;
       
   585 		if (_date - _news_type_data[_news_subtype_data[cur->subtype].type].age * _settings_client.gui.news_message_timeout > cur->date) DeleteNewsItem(cur);
       
   586 	}
       
   587 }
       
   588 
   591 void NewsLoop()
   589 void NewsLoop()
   592 {
   590 {
   593 	/* no news item yet */
   591 	/* no news item yet */
   594 	if (_total_news == 0) return;
   592 	if (_total_news == 0) return;
   595 
   593 
       
   594 	static byte _last_clean_month = 0;
       
   595 
       
   596 	if (_last_clean_month != _cur_month) {
       
   597 		RemoveOldNewsItems();
       
   598 		_last_clean_month = _cur_month;
       
   599 	}
       
   600 
   596 	if (ReadyForNextItem()) MoveToNextItem();
   601 	if (ReadyForNextItem()) MoveToNextItem();
   597 }
   602 }
   598 
   603 
   599 /** Do a forced show of a specific message */
   604 /** Do a forced show of a specific message */
   600 static void ShowNewsMessage(NewsID i)
   605 static void ShowNewsMessage(NewsItem *ni)
   601 {
   606 {
   602 	if (_total_news == 0) return;
   607 	assert(_total_news != 0);
   603 
   608 
   604 	/* Delete the news window */
   609 	/* Delete the news window */
   605 	DeleteWindowById(WC_NEWS_WINDOW, 0);
   610 	DeleteWindowById(WC_NEWS_WINDOW, 0);
   606 
   611 
   607 	/* setup forced news item */
   612 	/* setup forced news item */
   608 	_forced_news = i;
   613 	_forced_news = ni;
   609 
   614 
   610 	if (_forced_news != INVALID_NEWS) {
   615 	if (_forced_news != NULL) {
   611 		NewsItem *ni = &_news_items[_forced_news];
       
   612 		ni->duration = 555;
   616 		ni->duration = 555;
   613 		ni->flags |= NF_FORCE_BIG;
   617 		ni->flags |= NF_FORCE_BIG;
   614 		DeleteWindowById(WC_NEWS_WINDOW, 0);
   618 		DeleteWindowById(WC_NEWS_WINDOW, 0);
   615 		ShowNewspaper(ni);
   619 		ShowNewspaper(ni);
   616 	}
   620 	}
   617 }
   621 }
   618 
   622 
   619 /** Show previous news item */
   623 /** Show previous news item */
   620 void ShowLastNewsMessage()
   624 void ShowLastNewsMessage()
   621 {
   625 {
   622 	if (_forced_news == INVALID_NEWS) {
   626 	if (_total_news == 0) {
       
   627 		return;
       
   628 	} else if (_forced_news == NULL) {
   623 		/* Not forced any news yet, show the current one, unless a news window is
   629 		/* Not forced any news yet, show the current one, unless a news window is
   624 		 * open (which can only be the current one), then show the previous item */
   630 		 * open (which can only be the current one), then show the previous item */
   625 		const Window *w = FindWindowById(WC_NEWS_WINDOW, 0);
   631 		const Window *w = FindWindowById(WC_NEWS_WINDOW, 0);
   626 		ShowNewsMessage((w == NULL || (_current_news == _oldest_news)) ? _current_news : DecreaseIndex(_current_news));
   632 		ShowNewsMessage((w == NULL || (_current_news == _oldest_news)) ? _current_news : _current_news->prev);
   627 	} else if (_forced_news == _oldest_news) {
   633 	} else if (_forced_news == _oldest_news) {
   628 		/* We have reached the oldest news, start anew with the latest */
   634 		/* We have reached the oldest news, start anew with the latest */
   629 		ShowNewsMessage(_latest_news);
   635 		ShowNewsMessage(_latest_news);
   630 	} else {
   636 	} else {
   631 		/* 'Scrolling' through news history show each one in turn */
   637 		/* 'Scrolling' through news history show each one in turn */
   632 		ShowNewsMessage(DecreaseIndex(_forced_news));
   638 		ShowNewsMessage(_forced_news->prev);
   633 	}
   639 	}
   634 }
   640 }
   635 
   641 
   636 
       
   637 /* return news by number, with 0 being the most
       
   638  * recent news. Returns INVALID_NEWS if end of queue reached. */
       
   639 static NewsID getNews(NewsID i)
       
   640 {
       
   641 	if (i >= _total_news) return INVALID_NEWS;
       
   642 
       
   643 	if (_latest_news < i) {
       
   644 		i = _latest_news + _max_news_items - i;
       
   645 	} else {
       
   646 		i = _latest_news - i;
       
   647 	}
       
   648 
       
   649 	i %= _max_news_items;
       
   650 	return i;
       
   651 }
       
   652 
   642 
   653 /**
   643 /**
   654  * Draw an unformatted news message truncated to a maximum length. If
   644  * Draw an unformatted news message truncated to a maximum length. If
   655  * length exceeds maximum length it will be postfixed by '...'
   645  * length exceeds maximum length it will be postfixed by '...'
   656  * @param x,y position of the string
   646  * @param x,y position of the string
   713 
   703 
   714 		SetVScrollCount(this, _total_news);
   704 		SetVScrollCount(this, _total_news);
   715 		this->DrawWidgets();
   705 		this->DrawWidgets();
   716 
   706 
   717 		if (_total_news == 0) return;
   707 		if (_total_news == 0) return;
   718 		NewsID show = min(_total_news, this->vscroll.cap);
   708 
   719 
   709 		NewsItem *ni = _latest_news;
   720 		for (NewsID p = this->vscroll.pos; p < this->vscroll.pos + show; p++) {
   710 		for (int n = this->vscroll.pos; n > 0; n--) {
   721 			/* get news in correct order */
   711 			ni = ni->prev;
   722 			const NewsItem *ni = &_news_items[getNews(p)];
   712 			if (ni == NULL) return;
   723 
   713 		}
       
   714 
       
   715 		for (int n = this->vscroll.cap; n > 0; n--) {
   724 			SetDParam(0, ni->date);
   716 			SetDParam(0, ni->date);
   725 			DrawString(4, y, STR_SHORT_DATE, TC_WHITE);
   717 			DrawString(4, y, STR_SHORT_DATE, TC_WHITE);
   726 
   718 
   727 			DrawNewsString(82, y, TC_WHITE, ni, this->width - 95);
   719 			DrawNewsString(82, y, TC_WHITE, ni, this->width - 95);
   728 			y += 12;
   720 			y += 12;
       
   721 
       
   722 			ni = ni->prev;
       
   723 			if (ni == NULL) return;
   729 		}
   724 		}
   730 	}
   725 	}
   731 
   726 
   732 	virtual void OnClick(Point pt, int widget)
   727 	virtual void OnClick(Point pt, int widget)
   733 	{
   728 	{
   734 		if (widget == 3) {
   729 		if (widget == 3) {
   735 			int y = (pt.y - 19) / 12;
   730 			NewsItem *ni = _latest_news;
   736 			NewsID p = getNews(y + this->vscroll.pos);
   731 			if (ni == NULL) return;
   737 
   732 
   738 			if (p != INVALID_NEWS) ShowNewsMessage(p);
   733 			for (int n = (pt.y - 19) / 12 + this->vscroll.pos; n > 0; n--) {
       
   734 				ni = ni->prev;
       
   735 				if (ni == NULL) return;
       
   736 			}
       
   737 
       
   738 			ShowNewsMessage(ni);
   739 		}
   739 		}
   740 	}
   740 	}
   741 
   741 
   742 	virtual void OnResize(Point new_size, Point delta)
   742 	virtual void OnResize(Point new_size, Point delta)
   743 	{
   743 	{
   974 void ShowMessageOptions()
   974 void ShowMessageOptions()
   975 {
   975 {
   976 	DeleteWindowById(WC_GAME_OPTIONS, 0);
   976 	DeleteWindowById(WC_GAME_OPTIONS, 0);
   977 	new MessageOptionsWindow(&_message_options_desc);
   977 	new MessageOptionsWindow(&_message_options_desc);
   978 }
   978 }
   979 
       
   980 
       
   981 void DeleteVehicleNews(VehicleID vid, StringID news)
       
   982 {
       
   983 	for (NewsID n = _oldest_news; _latest_news != INVALID_NEWS; n = IncreaseIndex(n)) {
       
   984 		const NewsItem *ni = &_news_items[n];
       
   985 
       
   986 		if (ni->flags & NF_VEHICLE &&
       
   987 				ni->data_a == vid &&
       
   988 				(news == INVALID_STRING_ID || ni->string_id == news)) {
       
   989 			/* If we delete a forced news and it is just before the current news
       
   990 			 * then we need to advance to the next news (if any) */
       
   991 			if (_forced_news == n) MoveToNextItem();
       
   992 			if (_forced_news == INVALID_NEWS && _current_news == n) MoveToNextItem();
       
   993 			_total_news--;
       
   994 
       
   995 			/* If this is the last news item, invalidate _latest_news */
       
   996 			if (_total_news == 0) {
       
   997 				assert(_latest_news == _oldest_news);
       
   998 				_latest_news = INVALID_NEWS;
       
   999 				_current_news = INVALID_NEWS;
       
  1000 			}
       
  1001 
       
  1002 			/* Since we only imitate a FIFO removing an arbitrary element does need
       
  1003 			 * some magic. Remove the item by shifting head towards the tail. eg
       
  1004 			 *    oldest    remove  last
       
  1005 			 *        |        |     |
       
  1006 			 * [------O--------n-----L--]
       
  1007 			 * will become (change dramatized to make clear)
       
  1008 			 * [---------O-----------L--]
       
  1009 			 * We also need an update of the current, forced and visible (open window)
       
  1010 			 * news's as this shifting could change the items they were pointing to */
       
  1011 			if (_total_news != 0) {
       
  1012 				NewsWindow *w = dynamic_cast<NewsWindow*>(FindWindowById(WC_NEWS_WINDOW, 0));
       
  1013 				NewsID visible_news = (w != NULL) ? (NewsID)(w->ni - _news_items) : INVALID_NEWS;
       
  1014 
       
  1015 				for (NewsID i = n;; i = DecreaseIndex(i)) {
       
  1016 					_news_items[i] = _news_items[DecreaseIndex(i)];
       
  1017 
       
  1018 					if (i != _latest_news) {
       
  1019 						if (i == _current_news) _current_news = IncreaseIndex(_current_news);
       
  1020 						if (i == _forced_news) _forced_news = IncreaseIndex(_forced_news);
       
  1021 						if (i == visible_news) w->ni = &_news_items[IncreaseIndex(visible_news)];
       
  1022 					}
       
  1023 
       
  1024 					if (i == _oldest_news) break;
       
  1025 				}
       
  1026 				_oldest_news = IncreaseIndex(_oldest_news);
       
  1027 			}
       
  1028 
       
  1029 			/*DEBUG(misc, 0, "-cur %3d, old %2d, lat %3d, for %3d, tot %2d",
       
  1030 			  _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/
       
  1031 
       
  1032 			Window *w = FindWindowById(WC_MESSAGE_HISTORY, 0);
       
  1033 			if (w != NULL) {
       
  1034 				w->SetDirty();
       
  1035 				w->vscroll.count = _total_news;
       
  1036 			}
       
  1037 		}
       
  1038 
       
  1039 		if (n == _latest_news) break;
       
  1040 	}
       
  1041 }