@ -24,52 +24,24 @@
# include "table/sprites.h"
# include "table/sprites.h"
# include "table/strings.h"
# include "table/strings.h"
/** @file news_gui.cpp
*
* News system is realized as a FIFO queue ( in an array )
* The positions in the queue can ' t be rearranged , we only access
* the array elements through pointers to the elements . Once the
* array is full , the oldest entry ( \ a _oldest_news ) is being overwritten
* by the newest ( \ a _latest_news ) .
*
* \ verbatim
* oldest current lastest
* | | |
* [ O - - - - - - - - - - - - F - - - - - - - - - - - - - C - - - - - - - - - L ]
* |
* forced
* \ endverbatim
*
* Of course by using an array we can have situations like
*
* \ verbatim
* [ - - - - L O - - - - - F - - - - - - - - - C - - - - - - - - - - - - - - - - - ]
* This is where we have wrapped around the array and have
* ( MAX_NEWS - O ) + L news items
* \ endverbatim
*/
# define NB_WIDG_PER_SETTING 4
# define NB_WIDG_PER_SETTING 4
typedef byte NewsID ;
# define INVALID_NEWS 255
NewsItem _statusbar_news_item ;
NewsItem _statusbar_news_item ;
bool _news_ticker_sound ;
bool _news_ticker_sound ;
static NewsItem * _news_items = NULL ; ///< The news FIFO queue
static uint _max_news_items = 0 ; ///< size of news FIFO queue
static uint MIN_NEWS_AMOUNT = 30 ; ///< prefered minimum amount of news messages
static NewsID _current_news = INVALID_NEWS ; ///< points to news item that should be shown next
static uint _total_news = 0 ; ///< current number of news items
static NewsI D _oldest_news = 0 ; ///< points to first item in fifo queue
static NewsItem * _oldest_news = NULL ; ///< head of news items queue
static NewsI D _latest_news = INVALID_NEWS ; ///< points to last item in fifo queue
static NewsItem * _latest_news = NULL ; ///< tail of news items queue
/** Forced news item.
/** Forced news item.
* Users can force an item by accessing the history or " last message " .
* Users can force an item by accessing the history or " last message " .
* If the message being shown was forced by the user , its index is stored in
* If the message being shown was forced by the user , a pointer is stored
* _forced_news. Otherwise , \ a _forced_news variable is INVALID_NEWS . */
* in _forced_news . Otherwise , \ a _forced_news variable is NULL . */
static NewsI D _forced_news = INVALID_NEWS ;
static NewsItem * _forced_news = NULL ; ///< item the user has asked for
static uint _total_news = 0 ; ///< Number of news items in FIFO queue @see _news_items
/** Current news item (last item shown regularly). */
static void MoveToNextItem ( ) ;
static NewsItem * _current_news = NULL ;
typedef void DrawNewsCallbackProc ( struct Window * w , const NewsItem * ni ) ;
typedef void DrawNewsCallbackProc ( struct Window * w , const NewsItem * ni ) ;
@ -202,7 +174,7 @@ struct NewsWindow : Window {
const Window * w = FindWindowById ( WC_SEND_NETWORK_MSG , 0 ) ;
const Window * w = FindWindowById ( WC_SEND_NETWORK_MSG , 0 ) ;
this - > chat_height = ( w ! = NULL ) ? w - > height : 0 ;
this - > chat_height = ( w ! = NULL ) ? w - > height : 0 ;
this - > ni = & _news_items [ _forced_news = = INVALID_NEWS ? _current_news : _forced_news ] ;
this - > ni = _forced_news = = NULL ? _current_news : _forced_news ;
this - > flags4 | = WF_DISABLE_VP_SCROLL ;
this - > flags4 | = WF_DISABLE_VP_SCROLL ;
this - > FindWindowPlacementAndResize ( desc ) ;
this - > FindWindowPlacementAndResize ( desc ) ;
@ -288,7 +260,7 @@ struct NewsWindow : Window {
case 1 :
case 1 :
this - > ni - > duration = 0 ;
this - > ni - > duration = 0 ;
delete this ;
delete this ;
_forced_news = INVALID_NEWS ;
_forced_news = NULL ;
break ;
break ;
case 0 :
case 0 :
@ -425,9 +397,6 @@ static void ShowNewspaper(NewsItem *ni)
}
}
break ;
break ;
}
}
/*DEBUG(misc, 0, " cur %3d, old %2d, lat %3d, for %3d, tot %2d",
_current_news , _oldest_news , _latest_news , _forced_news , _total_news ) ; */
}
}
/** Show news item in the ticker */
/** Show news item in the ticker */
@ -442,34 +411,72 @@ static void ShowTicker(const NewsItem *ni)
/** Initialize the news-items data structures */
/** Initialize the news-items data structures */
void InitNewsItemStructs ( )
void InitNewsItemStructs ( )
{
{
free ( _news_items ) ;
for ( NewsItem * ni = _oldest_news ; ni ! = NULL ; ) {
_max_news_items = max ( ScaleByMapSize ( 30 ) , 30U ) ;
NewsItem * next = ni - > next ;
_news_items = CallocT < NewsItem > ( _max_news_items ) ;
delete ni ;
_current_news = INVALID_NEWS ;
ni = next ;
_oldest_news = 0 ;
}
_latest_news = INVALID_NEWS ;
_forced_news = INVALID_NEWS ;
_total_news = 0 ;
_total_news = 0 ;
_oldest_news = NULL ;
_latest_news = NULL ;
_forced_news = NULL ;
_current_news = NULL ;
}
}
/**
/**
* Return the correct index in the pseudo - fifo
* Are we ready to show another news item ?
* queue and deals with overflows when increasing the index
* Only if nothing is in the newsticker and no newspaper is displayed
*/
*/
static inline NewsID IncreaseIndex ( NewsID i )
static bool ReadyForNextItem ( )
{
{
assert ( i ! = INVALID_NEWS ) ;
NewsItem * ni = _forced_news = = NULL ? _current_news : _forced_news ;
return ( i + 1 ) % _max_news_items ;
if ( ni = = NULL ) return true ;
/* Ticker message
* Check if the status bar message is still being displayed ? */
if ( IsNewsTickerShown ( ) ) return false ;
/* Newspaper message, decrement duration counter */
if ( ni - > duration ! = 0 ) ni - > duration - - ;
/* neither newsticker nor newspaper are running */
return ( ni - > duration = = 0 | | FindWindowById ( WC_NEWS_WINDOW , 0 ) = = NULL ) ;
}
}
/**
/** Move to the next news item */
* Return the correct index in the pseudo - fifo
static void MoveToNextItem ( )
* queue and deals with overflows when decreasing the index
*/
static inline NewsID DecreaseIndex ( NewsID i )
{
{
assert ( i ! = INVALID_NEWS ) ;
DeleteWindowById ( WC_NEWS_WINDOW , 0 ) ;
return ( i + _max_news_items - 1 ) % _max_news_items ;
_forced_news = NULL ;
/* if we're not at the last item, then move on */
if ( _current_news ! = _latest_news ) {
_current_news = ( _current_news = = NULL ) ? _oldest_news : _current_news - > next ;
NewsItem * ni = _current_news ;
const NewsType type = _news_subtype_data [ ni - > subtype ] . type ;
/* check the date, don't show too old items */
if ( _date - _news_type_data [ type ] . age > ni - > date ) return ;
switch ( _news_type_data [ type ] . display ) {
default : NOT_REACHED ( ) ;
case ND_OFF : // Off - show nothing only a small reminder in the status bar
InvalidateWindowData ( WC_STATUS_BAR , 0 , SBI_SHOW_REMINDER ) ;
break ;
case ND_SUMMARY : // Summary - show ticker, but if forced big, cascade to full
if ( ! ( ni - > flags & NF_FORCE_BIG ) ) {
ShowTicker ( ni ) ;
break ;
}
/* Fallthrough */
case ND_FULL : // Full - show newspaper
ShowNewspaper ( ni ) ;
break ;
}
}
}
}
/**
/**
@ -485,31 +492,8 @@ void AddNewsItem(StringID string, NewsSubtype subtype, uint data_a, uint data_b)
{
{
if ( _game_mode = = GM_MENU ) return ;
if ( _game_mode = = GM_MENU ) return ;
/* check the rare case that the oldest (to be overwritten) news item is open */
/* Create new news item node */
if ( _total_news = = _max_news_items & & ( _oldest_news = = _current_news | | _oldest_news = = _forced_news ) ) {
NewsItem * ni = new NewsItem ;
MoveToNextItem ( ) ;
}
if ( _total_news < _max_news_items ) _total_news + + ;
/* Increase _latest_news. If we have no news yet, use _oldest news as an
* index . We cannot use 0 as _oldest_news can jump around due to
* DeleteVehicleNews */
NewsID l_news = _latest_news ;
_latest_news = ( _latest_news = = INVALID_NEWS ) ? _oldest_news : IncreaseIndex ( _latest_news ) ;
/* If the fifo-buffer is full, overwrite the oldest entry */
if ( l_news ! = INVALID_NEWS & & _latest_news = = _oldest_news ) {
assert ( _total_news = = _max_news_items ) ;
_oldest_news = IncreaseIndex ( _oldest_news ) ;
}
/*DEBUG(misc, 0, "+cur %3d, old %2d, lat %3d, for %3d, tot %2d",
_current_news , _oldest_news , _latest_news , _forced_news , _total_news ) ; */
/* Add news to _latest_news */
NewsItem * ni = & _news_items [ _latest_news ] ;
memset ( ni , 0 , sizeof ( * ni ) ) ;
ni - > string_id = string ;
ni - > string_id = string ;
ni - > subtype = subtype ;
ni - > subtype = subtype ;
@ -523,128 +507,82 @@ void AddNewsItem(StringID string, NewsSubtype subtype, uint data_a, uint data_b)
ni - > date = _date ;
ni - > date = _date ;
CopyOutDParam ( ni - > params , 0 , lengthof ( ni - > params ) ) ;
CopyOutDParam ( ni - > params , 0 , lengthof ( ni - > params ) ) ;
Window * w = FindWindowById ( WC_MESSAGE_HISTORY , 0 ) ;
if ( _total_news + + = = 0 ) {
if ( w = = NULL ) return ;
assert ( _oldest_news = = NULL ) ;
w - > SetDirty ( ) ;
_oldest_news = ni ;
w - > vscroll . count = _total_news ;
ni - > prev = NULL ;
} else {
assert ( _latest_news - > next = = NULL ) ;
_latest_news - > next = ni ;
ni - > prev = _latest_news ;
}
ni - > next = NULL ;
_latest_news = ni ;
InvalidateWindow ( WC_MESSAGE_HISTORY , 0 ) ;
}
}
void DeleteVehicleNews ( VehicleID vid , StringID news )
/** Delete a news item from the queue */
static void DeleteNewsItem ( NewsItem * ni )
{
{
for ( NewsID n = _oldest_news ; _latest_news ! = INVALID_NEWS ; n = IncreaseIndex ( n ) ) {
if ( _forced_news = = ni ) {
const NewsItem * ni = & _news_items [ n ] ;
/* about to remove the currently forced item; skip to next */
MoveToNextItem ( ) ;
}
if ( ni - > flags & NF_VEHICLE & &
if ( ( _current_news = = ni ) & & ( FindWindowById ( WC_MESSAGE_HISTORY , 0 ) ! = NULL ) ) {
ni - > data_a = = vid & &
/* about to remove the currently displayed item; also skip */
( news = = INVALID_STRING_ID | | ni - > string_id = = news ) ) {
MoveToNextItem ( ) ;
/* If we delete a forced news and it is just before the current news
}
* then we need to advance to the next news ( if any ) */
if ( _forced_news = = n ) MoveToNextItem ( ) ;
if ( _forced_news = = INVALID_NEWS & & _current_news = = n ) MoveToNextItem ( ) ;
_total_news - - ;
/* If this is the last news item, invalidate _latest_news */
if ( _total_news = = 0 ) {
assert ( _latest_news = = _oldest_news ) ;
_latest_news = INVALID_NEWS ;
_current_news = INVALID_NEWS ;
}
/* Since we only imitate a FIFO removing an arbitrary element does need
/* delete item */
* some magic . Remove the item by shifting head towards the tail . eg
* oldest remove last
* | | |
* [ - - - - - - O - - - - - - - - n - - - - - L - - ]
* will become ( change dramatized to make clear )
* [ - - - - - - - - - O - - - - - - - - - - - L - - ]
* We also need an update of the current , forced and visible ( open window )
* news ' s as this shifting could change the items they were pointing to */
if ( _total_news ! = 0 ) {
NewsWindow * w = dynamic_cast < NewsWindow * > ( FindWindowById ( WC_NEWS_WINDOW , 0 ) ) ;
NewsID visible_news = ( w ! = NULL ) ? ( NewsID ) ( w - > ni - _news_items ) : INVALID_NEWS ;
for ( NewsID i = n ; ; i = DecreaseIndex ( i ) ) {
_news_items [ i ] = _news_items [ DecreaseIndex ( i ) ] ;
if ( i ! = _latest_news ) {
if ( i = = _current_news ) _current_news = IncreaseIndex ( _current_news ) ;
if ( i = = _forced_news ) _forced_news = IncreaseIndex ( _forced_news ) ;
if ( i = = visible_news ) w - > ni = & _news_items [ IncreaseIndex ( visible_news ) ] ;
}
if ( i = = _oldest_news ) break ;
if ( ni - > prev ! = NULL ) {
}
ni - > prev - > next = ni - > next ;
_oldest_news = IncreaseIndex ( _oldest_news ) ;
} else {
}
assert ( _oldest_news = = ni ) ;
_oldest_news = ni - > next ;
}
/*DEBUG(misc, 0, "-cur %3d, old %2d, lat %3d, for %3d, tot %2d",
if ( ni - > next ! = NULL ) {
_current_news , _oldest_news , _latest_news , _forced_news , _total_news ) ; */
ni - > next - > prev = ni - > prev ;
} else {
assert ( _latest_news = = ni ) ;
_latest_news = ni - > prev ;
}
Window * w = FindWindowById ( WC_MESSAGE_HISTORY , 0 ) ;
if ( _current_news = = ni ) _current_news = ni - > prev ;
if ( w ! = NULL ) {
_total_news - - ;
w - > SetDirty ( ) ;
delete ni ;
w - > vscroll . count = _total_news ;
}
}
if ( n = = _latest_news ) break ;
InvalidateWindow ( WC_MESSAGE_HISTORY , 0 ) ;
}
}
}
/**
void DeleteVehicleNews ( VehicleID vid , StringID news )
* Are we ready to show another news item ?
* Only if nothing is in the newsticker and no newspaper is displayed
*/
static bool ReadyForNextItem ( )
{
{
NewsID item = ( _forced_news = = INVALID_NEWS ) ? _current_news : _forced_news ;
NewsItem * ni = _oldest_news ;
if ( item > = _max_news_items ) return true ;
NewsItem * ni = & _news_items [ item ] ;
/* Ticker message
* Check if the status bar message is still being displayed ? */
if ( IsNewsTickerShown ( ) ) return false ;
/* Newspaper message, decrement duration counter */
if ( ni - > duration ! = 0 ) ni - > duration - - ;
/* neither newsticker nor newspaper are running */
while ( ni ! = NULL ) {
return ( ni - > duration = = 0 | | FindWindowById ( WC_NEWS_WINDOW , 0 ) = = NULL ) ;
if ( ni - > flags & NF_VEHICLE & &
ni - > data_a = = vid & &
( news = = INVALID_STRING_ID | | ni - > string_id = = news ) ) {
/* grab a pointer to the next item before ni is freed */
NewsItem * p = ni - > next ;
DeleteNewsItem ( ni ) ;
ni = p ;
} else {
ni = ni - > next ;
}
}
}
}
/** Move to the next news item */
void RemoveOldNewsItems ( )
static void MoveToNextItem ( )
{
{
DeleteWindowById ( WC_NEWS_WINDOW , 0 ) ;
NewsItem * next ;
_forced_news = INVALID_NEWS ;
for ( NewsItem * cur = _oldest_news ; _total_news > MIN_NEWS_AMOUNT & & cur ! = NULL ; cur = next ) {
next = cur - > next ;
/* if we're not at the last item, then move on */
if ( _date - _news_type_data [ _news_subtype_data [ cur - > subtype ] . type ] . age * _settings . gui . news_message_timeout > cur - > date ) DeleteNewsItem ( cur ) ;
if ( _current_news ! = _latest_news ) {
_current_news = ( _current_news = = INVALID_NEWS ) ? _oldest_news : IncreaseIndex ( _current_news ) ;
NewsItem * ni = & _news_items [ _current_news ] ;
const NewsType type = _news_subtype_data [ ni - > subtype ] . type ;
/* check the date, don't show too old items */
if ( _date - _news_type_data [ type ] . age > ni - > date ) return ;
switch ( _news_type_data [ type ] . display ) {
default : NOT_REACHED ( ) ;
case ND_OFF : // Off - show nothing only a small reminder in the status bar
InvalidateWindowData ( WC_STATUS_BAR , 0 , SBI_SHOW_REMINDER ) ;
break ;
case ND_SUMMARY : // Summary - show ticker, but if forced big, cascade to full
if ( ! ( ni - > flags & NF_FORCE_BIG ) ) {
ShowTicker ( ni ) ;
break ;
}
/* Fallthrough */
case ND_FULL : // Full - show newspaper
ShowNewspaper ( ni ) ;
break ;
}
}
}
}
}
@ -653,22 +591,28 @@ void NewsLoop()
/* no news item yet */
/* no news item yet */
if ( _total_news = = 0 ) return ;
if ( _total_news = = 0 ) return ;
static byte _last_clean_month = 0 ;
if ( _last_clean_month ! = _cur_month ) {
RemoveOldNewsItems ( ) ;
_last_clean_month = _cur_month ;
}
if ( ReadyForNextItem ( ) ) MoveToNextItem ( ) ;
if ( ReadyForNextItem ( ) ) MoveToNextItem ( ) ;
}
}
/** Do a forced show of a specific message */
/** Do a forced show of a specific message */
static void ShowNewsMessage ( NewsID i )
static void ShowNewsMessage ( NewsI tem * n i)
{
{
if ( _total_news = = 0 ) return ;
assert ( _total_news ! = 0 ) ;
/* Delete the news window */
/* Delete the news window */
DeleteWindowById ( WC_NEWS_WINDOW , 0 ) ;
DeleteWindowById ( WC_NEWS_WINDOW , 0 ) ;
/* setup forced news item */
/* setup forced news item */
_forced_news = i;
_forced_news = n i;
if ( _forced_news ! = INVALID_NEWS ) {
if ( _forced_news ! = NULL ) {
NewsItem * ni = & _news_items [ _forced_news ] ;
ni - > duration = 555 ;
ni - > duration = 555 ;
ni - > flags | = NF_FORCE_BIG ;
ni - > flags | = NF_FORCE_BIG ;
DeleteWindowById ( WC_NEWS_WINDOW , 0 ) ;
DeleteWindowById ( WC_NEWS_WINDOW , 0 ) ;
@ -679,37 +623,23 @@ static void ShowNewsMessage(NewsID i)
/** Show previous news item */
/** Show previous news item */
void ShowLastNewsMessage ( )
void ShowLastNewsMessage ( )
{
{
if ( _forced_news = = INVALID_NEWS ) {
if ( _total_news = = 0 ) {
return ;
} else if ( _forced_news = = NULL ) {
/* Not forced any news yet, show the current one, unless a news window is
/* Not forced any news yet, show the current one, unless a news window is
* open ( which can only be the current one ) , then show the previous item */
* open ( which can only be the current one ) , then show the previous item */
const Window * w = FindWindowById ( WC_NEWS_WINDOW , 0 ) ;
const Window * w = FindWindowById ( WC_NEWS_WINDOW , 0 ) ;
ShowNewsMessage ( ( w = = NULL | | ( _current_news = = _oldest_news ) ) ? _current_news : DecreaseIndex( _current_news ) ) ;
ShowNewsMessage ( ( w = = NULL | | ( _current_news = = _oldest_news ) ) ? _current_news : _current_news- > prev ) ;
} else if ( _forced_news = = _oldest_news ) {
} else if ( _forced_news = = _oldest_news ) {
/* We have reached the oldest news, start anew with the latest */
/* We have reached the oldest news, start anew with the latest */
ShowNewsMessage ( _latest_news ) ;
ShowNewsMessage ( _latest_news ) ;
} else {
} else {
/* 'Scrolling' through news history show each one in turn */
/* 'Scrolling' through news history show each one in turn */
ShowNewsMessage ( DecreaseIndex( _forced_news ) ) ;
ShowNewsMessage ( _forced_news- > prev ) ;
}
}
}
}
/* return news by number, with 0 being the most
* recent news . Returns INVALID_NEWS if end of queue reached . */
static NewsID getNews ( NewsID i )
{
if ( i > = _total_news ) return INVALID_NEWS ;
if ( _latest_news < i ) {
i = _latest_news + _max_news_items - i ;
} else {
i = _latest_news - i ;
}
i % = _max_news_items ;
return i ;
}
/**
/**
* Draw an unformatted news message truncated to a maximum length . If
* Draw an unformatted news message truncated to a maximum length . If
* length exceeds maximum length it will be postfixed by ' . . . '
* length exceeds maximum length it will be postfixed by ' . . . '
@ -775,27 +705,37 @@ struct MessageHistoryWindow : Window {
this - > DrawWidgets ( ) ;
this - > DrawWidgets ( ) ;
if ( _total_news = = 0 ) return ;
if ( _total_news = = 0 ) return ;
NewsID show = min ( _total_news , this - > vscroll . cap ) ;
for ( NewsID p = this - > vscroll . pos ; p < this - > vscroll . pos + show ; p + + ) {
NewsItem * ni = _latest_news ;
/* get news in correct order */
for ( int n = this - > vscroll . pos ; n > 0 ; n - - ) {
const NewsItem * ni = & _news_items [ getNews ( p ) ] ;
ni = ni - > prev ;
if ( ni = = NULL ) return ;
}
for ( int n = this - > vscroll . cap ; n > 0 ; n - - ) {
SetDParam ( 0 , ni - > date ) ;
SetDParam ( 0 , ni - > date ) ;
DrawString ( 4 , y , STR_SHORT_DATE , TC_WHITE ) ;
DrawString ( 4 , y , STR_SHORT_DATE , TC_WHITE ) ;
DrawNewsString ( 82 , y , TC_WHITE , ni , this - > width - 95 ) ;
DrawNewsString ( 82 , y , TC_WHITE , ni , this - > width - 95 ) ;
y + = 12 ;
y + = 12 ;
ni = ni - > prev ;
if ( ni = = NULL ) return ;
}
}
}
}
virtual void OnClick ( Point pt , int widget )
virtual void OnClick ( Point pt , int widget )
{
{
if ( widget = = 3 ) {
if ( widget = = 3 ) {
int y = ( pt . y - 19 ) / 12 ;
NewsItem * ni = _latest_news ;
NewsID p = getNews ( y + this - > vscroll . pos ) ;
if ( ni = = NULL ) return ;
for ( int n = ( pt . y - 19 ) / 12 + this - > vscroll . pos ; n > 0 ; n - - ) {
ni = ni - > prev ;
if ( ni = = NULL ) return ;
}
if ( p ! = INVALID_NEWS ) ShowNewsMessage ( p ) ;
ShowNewsMessage ( ni ) ;
}
}
}
}