src/news_gui.cpp
changeset 10767 83c8d60f4996
parent 10766 db9ef2a1830a
child 10775 7061477bfbcf
equal deleted inserted replaced
10766:db9ef2a1830a 10767:83c8d60f4996
    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 
   200 	NewsWindow(const WindowDesc *desc, NewsItem *ni) : Window(desc), ni(ni)
   172 	NewsWindow(const WindowDesc *desc, NewsItem *ni) : Window(desc), ni(ni)
   201 	{
   173 	{
   202 		const Window *w = FindWindowById(WC_SEND_NETWORK_MSG, 0);
   174 		const Window *w = FindWindowById(WC_SEND_NETWORK_MSG, 0);
   203 		this->chat_height = (w != NULL) ? w->height : 0;
   175 		this->chat_height = (w != NULL) ? w->height : 0;
   204 
   176 
   205 		this->ni = &_news_items[_forced_news == INVALID_NEWS ? _current_news : _forced_news];
   177 		this->ni = _forced_news == NULL ? _current_news : _forced_news;
   206 		this->flags4 |= WF_DISABLE_VP_SCROLL;
   178 		this->flags4 |= WF_DISABLE_VP_SCROLL;
   207 
   179 
   208 		this->FindWindowPlacementAndResize(desc);
   180 		this->FindWindowPlacementAndResize(desc);
   209 	}
   181 	}
   210 
   182 
   286 	{
   258 	{
   287 		switch (widget) {
   259 		switch (widget) {
   288 			case 1:
   260 			case 1:
   289 				this->ni->duration = 0;
   261 				this->ni->duration = 0;
   290 				delete this;
   262 				delete this;
   291 				_forced_news = INVALID_NEWS;
   263 				_forced_news = NULL;
   292 				break;
   264 				break;
   293 
   265 
   294 			case 0:
   266 			case 0:
   295 				if (this->ni->flags & NF_VEHICLE) {
   267 				if (this->ni->flags & NF_VEHICLE) {
   296 					Vehicle *v = GetVehicle(this->ni->data_a);
   268 					Vehicle *v = GetVehicle(this->ni->data_a);
   423 				InitializeWindowViewport(w, 3, 17, 274, 47,
   395 				InitializeWindowViewport(w, 3, 17, 274, 47,
   424 					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);
   425 			}
   397 			}
   426 			break;
   398 			break;
   427 	}
   399 	}
   428 
       
   429 	/*DEBUG(misc, 0, " cur %3d, old %2d, lat %3d, for %3d, tot %2d",
       
   430 	  _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/
       
   431 }
   400 }
   432 
   401 
   433 /** Show news item in the ticker */
   402 /** Show news item in the ticker */
   434 static void ShowTicker(const NewsItem *ni)
   403 static void ShowTicker(const NewsItem *ni)
   435 {
   404 {
   440 }
   409 }
   441 
   410 
   442 /** Initialize the news-items data structures */
   411 /** Initialize the news-items data structures */
   443 void InitNewsItemStructs()
   412 void InitNewsItemStructs()
   444 {
   413 {
   445 	free(_news_items);
   414 	for (NewsItem *ni = _oldest_news; ni != NULL; ) {
   446 	_max_news_items = max(ScaleByMapSize(30), 30U);
   415 		NewsItem *next = ni->next;
   447 	_news_items = CallocT<NewsItem>(_max_news_items);
   416 		delete ni;
   448 	_current_news = INVALID_NEWS;
   417 		ni = next;
   449 	_oldest_news = 0;
   418 	}
   450 	_latest_news = INVALID_NEWS;
   419 
   451 	_forced_news = INVALID_NEWS;
       
   452 	_total_news = 0;
   420 	_total_news = 0;
       
   421 	_oldest_news = NULL;
       
   422 	_latest_news = NULL;
       
   423 	_forced_news = NULL;
       
   424 	_current_news = NULL;
   453 }
   425 }
   454 
   426 
   455 /**
   427 /**
   456  * Return the correct index in the pseudo-fifo
   428  * Are we ready to show another news item?
   457  * queue and deals with overflows when increasing the index
   429  * Only if nothing is in the newsticker and no newspaper is displayed
   458  */
   430  */
   459 static inline NewsID IncreaseIndex(NewsID i)
   431 static bool ReadyForNextItem()
   460 {
   432 {
   461 	assert(i != INVALID_NEWS);
   433 	NewsItem *ni = _forced_news == NULL ? _current_news : _forced_news;
   462 	return (i + 1) % _max_news_items;
   434 	if (ni == NULL) return true;
   463 }
   435 
   464 
   436 	/* Ticker message
   465 /**
   437 	 * Check if the status bar message is still being displayed? */
   466  * Return the correct index in the pseudo-fifo
   438 	if (IsNewsTickerShown()) return false;
   467  * queue and deals with overflows when decreasing the index
   439 
   468  */
   440 	/* Newspaper message, decrement duration counter */
   469 static inline NewsID DecreaseIndex(NewsID i)
   441 	if (ni->duration != 0) ni->duration--;
   470 {
   442 
   471 	assert(i != INVALID_NEWS);
   443 	/* neither newsticker nor newspaper are running */
   472 	return (i + _max_news_items - 1) % _max_news_items;
   444 	return (ni->duration == 0 || FindWindowById(WC_NEWS_WINDOW, 0) == NULL);
       
   445 }
       
   446 
       
   447 /** Move to the next news item */
       
   448 static void MoveToNextItem()
       
   449 {
       
   450 	DeleteWindowById(WC_NEWS_WINDOW, 0);
       
   451 	_forced_news = NULL;
       
   452 
       
   453 	/* if we're not at the last item, then move on */
       
   454 	if (_current_news != _latest_news) {
       
   455 		_current_news = (_current_news == NULL) ? _oldest_news : _current_news->next;
       
   456 		NewsItem *ni = _current_news;
       
   457 		const NewsType type = _news_subtype_data[ni->subtype].type;
       
   458 
       
   459 		/* check the date, don't show too old items */
       
   460 		if (_date - _news_type_data[type].age > ni->date) return;
       
   461 
       
   462 		switch (_news_type_data[type].display) {
       
   463 			default: NOT_REACHED();
       
   464 			case ND_OFF: // Off - show nothing only a small reminder in the status bar
       
   465 				InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_REMINDER);
       
   466 				break;
       
   467 
       
   468 			case ND_SUMMARY: // Summary - show ticker, but if forced big, cascade to full
       
   469 				if (!(ni->flags & NF_FORCE_BIG)) {
       
   470 					ShowTicker(ni);
       
   471 					break;
       
   472 				}
       
   473 				/* Fallthrough */
       
   474 
       
   475 			case ND_FULL: // Full - show newspaper
       
   476 				ShowNewspaper(ni);
       
   477 				break;
       
   478 		}
       
   479 	}
   473 }
   480 }
   474 
   481 
   475 /**
   482 /**
   476  * Add a new newsitem to be shown.
   483  * Add a new newsitem to be shown.
   477  * @param string String to display
   484  * @param string String to display
   483  */
   490  */
   484 void AddNewsItem(StringID string, NewsSubtype subtype, uint data_a, uint data_b)
   491 void AddNewsItem(StringID string, NewsSubtype subtype, uint data_a, uint data_b)
   485 {
   492 {
   486 	if (_game_mode == GM_MENU) return;
   493 	if (_game_mode == GM_MENU) return;
   487 
   494 
   488 	/* check the rare case that the oldest (to be overwritten) news item is open */
   495 	/* Create new news item node */
   489 	if (_total_news == _max_news_items && (_oldest_news == _current_news || _oldest_news == _forced_news)) {
   496 	NewsItem *ni = new NewsItem;
   490 		MoveToNextItem();
       
   491 	}
       
   492 
       
   493 	if (_total_news < _max_news_items) _total_news++;
       
   494 
       
   495 	/* Increase _latest_news. If we have no news yet, use _oldest news as an
       
   496 	 * index. We cannot use 0 as _oldest_news can jump around due to
       
   497 	 * DeleteVehicleNews */
       
   498 	NewsID l_news = _latest_news;
       
   499 	_latest_news = (_latest_news == INVALID_NEWS) ? _oldest_news : IncreaseIndex(_latest_news);
       
   500 
       
   501 	/* If the fifo-buffer is full, overwrite the oldest entry */
       
   502 	if (l_news != INVALID_NEWS && _latest_news == _oldest_news) {
       
   503 		assert(_total_news == _max_news_items);
       
   504 		_oldest_news = IncreaseIndex(_oldest_news);
       
   505 	}
       
   506 
       
   507 	/*DEBUG(misc, 0, "+cur %3d, old %2d, lat %3d, for %3d, tot %2d",
       
   508 	  _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/
       
   509 
       
   510 	/* Add news to _latest_news */
       
   511 	NewsItem *ni = &_news_items[_latest_news];
       
   512 	memset(ni, 0, sizeof(*ni));
       
   513 
   497 
   514 	ni->string_id = string;
   498 	ni->string_id = string;
   515 	ni->subtype = subtype;
   499 	ni->subtype = subtype;
   516 	ni->flags = _news_subtype_data[subtype].flags;
   500 	ni->flags = _news_subtype_data[subtype].flags;
   517 
   501 
   521 	ni->data_a = data_a;
   505 	ni->data_a = data_a;
   522 	ni->data_b = data_b;
   506 	ni->data_b = data_b;
   523 	ni->date = _date;
   507 	ni->date = _date;
   524 	CopyOutDParam(ni->params, 0, lengthof(ni->params));
   508 	CopyOutDParam(ni->params, 0, lengthof(ni->params));
   525 
   509 
   526 	Window *w = FindWindowById(WC_MESSAGE_HISTORY, 0);
   510 	if (_total_news++ == 0) {
   527 	if (w == NULL) return;
   511 		assert(_oldest_news == NULL);
   528 	w->SetDirty();
   512 		_oldest_news = ni;
   529 	w->vscroll.count = _total_news;
   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);
   530 }
   560 }
   531 
   561 
   532 void DeleteVehicleNews(VehicleID vid, StringID news)
   562 void DeleteVehicleNews(VehicleID vid, StringID news)
   533 {
   563 {
   534 	for (NewsID n = _oldest_news; _latest_news != INVALID_NEWS; n = IncreaseIndex(n)) {
   564 	NewsItem *ni = _oldest_news;
   535 		const NewsItem *ni = &_news_items[n];
   565 
   536 
   566 	while (ni != NULL) {
   537 		if (ni->flags & NF_VEHICLE &&
   567 		if (ni->flags & NF_VEHICLE &&
   538 				ni->data_a == vid &&
   568 				ni->data_a == vid &&
   539 				(news == INVALID_STRING_ID || ni->string_id == news)) {
   569 				(news == INVALID_STRING_ID || ni->string_id == news)) {
   540 			/* If we delete a forced news and it is just before the current news
   570 			/* grab a pointer to the next item before ni is freed */
   541 			 * then we need to advance to the next news (if any) */
   571 			NewsItem *p = ni->next;
   542 			if (_forced_news == n) MoveToNextItem();
   572 			DeleteNewsItem(ni);
   543 			if (_forced_news == INVALID_NEWS && _current_news == n) MoveToNextItem();
   573 			ni = p;
   544 			_total_news--;
   574 		} else {
   545 
   575 			ni = ni->next;
   546 			/* If this is the last news item, invalidate _latest_news */
   576 		}
   547 			if (_total_news == 0) {
   577 	}
   548 				assert(_latest_news == _oldest_news);
   578 }
   549 				_latest_news = INVALID_NEWS;
   579 
   550 				_current_news = INVALID_NEWS;
   580 void RemoveOldNewsItems()
   551 			}
   581 {
   552 
   582 	NewsItem *next;
   553 			/* Since we only imitate a FIFO removing an arbitrary element does need
   583 	for (NewsItem *cur = _oldest_news; _total_news > MIN_NEWS_AMOUNT && cur != NULL; cur = next) {
   554 			 * some magic. Remove the item by shifting head towards the tail. eg
   584 		next = cur->next;
   555 			 *    oldest    remove  last
   585 		if (_date - _news_type_data[_news_subtype_data[cur->subtype].type].age * _settings.gui.news_message_timeout > cur->date) DeleteNewsItem(cur);
   556 			 *        |        |     |
       
   557 			 * [------O--------n-----L--]
       
   558 			 * will become (change dramatized to make clear)
       
   559 			 * [---------O-----------L--]
       
   560 			 * We also need an update of the current, forced and visible (open window)
       
   561 			 * news's as this shifting could change the items they were pointing to */
       
   562 			if (_total_news != 0) {
       
   563 				NewsWindow *w = dynamic_cast<NewsWindow*>(FindWindowById(WC_NEWS_WINDOW, 0));
       
   564 				NewsID visible_news = (w != NULL) ? (NewsID)(w->ni - _news_items) : INVALID_NEWS;
       
   565 
       
   566 				for (NewsID i = n;; i = DecreaseIndex(i)) {
       
   567 					_news_items[i] = _news_items[DecreaseIndex(i)];
       
   568 
       
   569 					if (i != _latest_news) {
       
   570 						if (i == _current_news) _current_news = IncreaseIndex(_current_news);
       
   571 						if (i == _forced_news) _forced_news = IncreaseIndex(_forced_news);
       
   572 						if (i == visible_news) w->ni = &_news_items[IncreaseIndex(visible_news)];
       
   573 					}
       
   574 
       
   575 					if (i == _oldest_news) break;
       
   576 				}
       
   577 				_oldest_news = IncreaseIndex(_oldest_news);
       
   578 			}
       
   579 
       
   580 			/*DEBUG(misc, 0, "-cur %3d, old %2d, lat %3d, for %3d, tot %2d",
       
   581 			  _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/
       
   582 
       
   583 			Window *w = FindWindowById(WC_MESSAGE_HISTORY, 0);
       
   584 			if (w != NULL) {
       
   585 				w->SetDirty();
       
   586 				w->vscroll.count = _total_news;
       
   587 			}
       
   588 		}
       
   589 
       
   590 		if (n == _latest_news) break;
       
   591 	}
       
   592 }
       
   593 
       
   594 /**
       
   595  * Are we ready to show another news item?
       
   596  * Only if nothing is in the newsticker and no newspaper is displayed
       
   597  */
       
   598 static bool ReadyForNextItem()
       
   599 {
       
   600 	NewsID item = (_forced_news == INVALID_NEWS) ? _current_news : _forced_news;
       
   601 
       
   602 	if (item >= _max_news_items) return true;
       
   603 	NewsItem *ni = &_news_items[item];
       
   604 
       
   605 	/* Ticker message
       
   606 	 * Check if the status bar message is still being displayed? */
       
   607 	if (IsNewsTickerShown()) return false;
       
   608 
       
   609 	/* Newspaper message, decrement duration counter */
       
   610 	if (ni->duration != 0) ni->duration--;
       
   611 
       
   612 	/* neither newsticker nor newspaper are running */
       
   613 	return (ni->duration == 0 || FindWindowById(WC_NEWS_WINDOW, 0) == NULL);
       
   614 }
       
   615 
       
   616 /** Move to the next news item */
       
   617 static void MoveToNextItem()
       
   618 {
       
   619 	DeleteWindowById(WC_NEWS_WINDOW, 0);
       
   620 	_forced_news = INVALID_NEWS;
       
   621 
       
   622 	/* if we're not at the last item, then move on */
       
   623 	if (_current_news != _latest_news) {
       
   624 		_current_news = (_current_news == INVALID_NEWS) ? _oldest_news : IncreaseIndex(_current_news);
       
   625 		NewsItem *ni = &_news_items[_current_news];
       
   626 		const NewsType type = _news_subtype_data[ni->subtype].type;
       
   627 
       
   628 		/* check the date, don't show too old items */
       
   629 		if (_date - _news_type_data[type].age > ni->date) return;
       
   630 
       
   631 		switch (_news_type_data[type].display) {
       
   632 			default: NOT_REACHED();
       
   633 			case ND_OFF: // Off - show nothing only a small reminder in the status bar
       
   634 				InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_REMINDER);
       
   635 				break;
       
   636 
       
   637 			case ND_SUMMARY: // Summary - show ticker, but if forced big, cascade to full
       
   638 				if (!(ni->flags & NF_FORCE_BIG)) {
       
   639 					ShowTicker(ni);
       
   640 					break;
       
   641 				}
       
   642 				/* Fallthrough */
       
   643 
       
   644 			case ND_FULL: // Full - show newspaper
       
   645 				ShowNewspaper(ni);
       
   646 				break;
       
   647 		}
       
   648 	}
   586 	}
   649 }
   587 }
   650 
   588 
   651 void NewsLoop()
   589 void NewsLoop()
   652 {
   590 {
   653 	/* no news item yet */
   591 	/* no news item yet */
   654 	if (_total_news == 0) return;
   592 	if (_total_news == 0) return;
   655 
   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 
   656 	if (ReadyForNextItem()) MoveToNextItem();
   601 	if (ReadyForNextItem()) MoveToNextItem();
   657 }
   602 }
   658 
   603 
   659 /** Do a forced show of a specific message */
   604 /** Do a forced show of a specific message */
   660 static void ShowNewsMessage(NewsID i)
   605 static void ShowNewsMessage(NewsItem *ni)
   661 {
   606 {
   662 	if (_total_news == 0) return;
   607 	assert(_total_news != 0);
   663 
   608 
   664 	/* Delete the news window */
   609 	/* Delete the news window */
   665 	DeleteWindowById(WC_NEWS_WINDOW, 0);
   610 	DeleteWindowById(WC_NEWS_WINDOW, 0);
   666 
   611 
   667 	/* setup forced news item */
   612 	/* setup forced news item */
   668 	_forced_news = i;
   613 	_forced_news = ni;
   669 
   614 
   670 	if (_forced_news != INVALID_NEWS) {
   615 	if (_forced_news != NULL) {
   671 		NewsItem *ni = &_news_items[_forced_news];
       
   672 		ni->duration = 555;
   616 		ni->duration = 555;
   673 		ni->flags |= NF_FORCE_BIG;
   617 		ni->flags |= NF_FORCE_BIG;
   674 		DeleteWindowById(WC_NEWS_WINDOW, 0);
   618 		DeleteWindowById(WC_NEWS_WINDOW, 0);
   675 		ShowNewspaper(ni);
   619 		ShowNewspaper(ni);
   676 	}
   620 	}
   677 }
   621 }
   678 
   622 
   679 /** Show previous news item */
   623 /** Show previous news item */
   680 void ShowLastNewsMessage()
   624 void ShowLastNewsMessage()
   681 {
   625 {
   682 	if (_forced_news == INVALID_NEWS) {
   626 	if (_total_news == 0) {
       
   627 		return;
       
   628 	} else if (_forced_news == NULL) {
   683 		/* 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
   684 		 * 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 */
   685 		const Window *w = FindWindowById(WC_NEWS_WINDOW, 0);
   631 		const Window *w = FindWindowById(WC_NEWS_WINDOW, 0);
   686 		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);
   687 	} else if (_forced_news == _oldest_news) {
   633 	} else if (_forced_news == _oldest_news) {
   688 		/* We have reached the oldest news, start anew with the latest */
   634 		/* We have reached the oldest news, start anew with the latest */
   689 		ShowNewsMessage(_latest_news);
   635 		ShowNewsMessage(_latest_news);
   690 	} else {
   636 	} else {
   691 		/* 'Scrolling' through news history show each one in turn */
   637 		/* 'Scrolling' through news history show each one in turn */
   692 		ShowNewsMessage(DecreaseIndex(_forced_news));
   638 		ShowNewsMessage(_forced_news->prev);
   693 	}
   639 	}
   694 }
   640 }
   695 
   641 
   696 
       
   697 /* return news by number, with 0 being the most
       
   698  * recent news. Returns INVALID_NEWS if end of queue reached. */
       
   699 static NewsID getNews(NewsID i)
       
   700 {
       
   701 	if (i >= _total_news) return INVALID_NEWS;
       
   702 
       
   703 	if (_latest_news < i) {
       
   704 		i = _latest_news + _max_news_items - i;
       
   705 	} else {
       
   706 		i = _latest_news - i;
       
   707 	}
       
   708 
       
   709 	i %= _max_news_items;
       
   710 	return i;
       
   711 }
       
   712 
   642 
   713 /**
   643 /**
   714  * Draw an unformatted news message truncated to a maximum length. If
   644  * Draw an unformatted news message truncated to a maximum length. If
   715  * length exceeds maximum length it will be postfixed by '...'
   645  * length exceeds maximum length it will be postfixed by '...'
   716  * @param x,y position of the string
   646  * @param x,y position of the string
   773 
   703 
   774 		SetVScrollCount(this, _total_news);
   704 		SetVScrollCount(this, _total_news);
   775 		this->DrawWidgets();
   705 		this->DrawWidgets();
   776 
   706 
   777 		if (_total_news == 0) return;
   707 		if (_total_news == 0) return;
   778 		NewsID show = min(_total_news, this->vscroll.cap);
   708 
   779 
   709 		NewsItem *ni = _latest_news;
   780 		for (NewsID p = this->vscroll.pos; p < this->vscroll.pos + show; p++) {
   710 		for (int n = this->vscroll.pos; n > 0; n--) {
   781 			/* get news in correct order */
   711 			ni = ni->prev;
   782 			const NewsItem *ni = &_news_items[getNews(p)];
   712 			if (ni == NULL) return;
   783 
   713 		}
       
   714 
       
   715 		for (int n = this->vscroll.cap; n > 0; n--) {
   784 			SetDParam(0, ni->date);
   716 			SetDParam(0, ni->date);
   785 			DrawString(4, y, STR_SHORT_DATE, TC_WHITE);
   717 			DrawString(4, y, STR_SHORT_DATE, TC_WHITE);
   786 
   718 
   787 			DrawNewsString(82, y, TC_WHITE, ni, this->width - 95);
   719 			DrawNewsString(82, y, TC_WHITE, ni, this->width - 95);
   788 			y += 12;
   720 			y += 12;
       
   721 
       
   722 			ni = ni->prev;
       
   723 			if (ni == NULL) return;
   789 		}
   724 		}
   790 	}
   725 	}
   791 
   726 
   792 	virtual void OnClick(Point pt, int widget)
   727 	virtual void OnClick(Point pt, int widget)
   793 	{
   728 	{
   794 		if (widget == 3) {
   729 		if (widget == 3) {
   795 			int y = (pt.y - 19) / 12;
   730 			NewsItem *ni = _latest_news;
   796 			NewsID p = getNews(y + this->vscroll.pos);
   731 			if (ni == NULL) return;
   797 
   732 
   798 			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);
   799 		}
   739 		}
   800 	}
   740 	}
   801 
   741 
   802 	virtual void OnResize(Point new_size, Point delta)
   742 	virtual void OnResize(Point new_size, Point delta)
   803 	{
   743 	{