Codechange: Use std::list for News Items. (#12338)

This commit is contained in:
Peter Nelson 2024-04-04 07:53:14 +01:00 committed by GitHub
parent 08cf106fc6
commit f6a88e40a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 155 additions and 196 deletions

View File

@ -56,11 +56,12 @@ static void SurveyRecentNews(nlohmann::json &json)
json = nlohmann::json::array(); json = nlohmann::json::array();
int i = 0; int i = 0;
for (NewsItem *news = _latest_news; i < 32 && news != nullptr; news = news->prev, i++) { for (const auto &news : GetNews()) {
TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(news->date); TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(news.date);
json.push_back(fmt::format("({}-{:02}-{:02}) StringID: {}, Type: {}, Ref1: {}, {}, Ref2: {}, {}", json.push_back(fmt::format("({}-{:02}-{:02}) StringID: {}, Type: {}, Ref1: {}, {}, Ref2: {}, {}",
ymd.year, ymd.month + 1, ymd.day, news->string_id, news->type, ymd.year, ymd.month + 1, ymd.day, news.string_id, news.type,
news->reftype1, news->ref1, news->reftype2, news->ref2)); news.reftype1, news.ref1, news.reftype2, news.ref2));
if (++i > 32) break;
} }
} }

View File

@ -55,7 +55,7 @@ inline void AddIndustryNewsItem(StringID string, NewsType type, IndustryID indus
void NewsLoop(); void NewsLoop();
void InitNewsItemStructs(); void InitNewsItemStructs();
extern const NewsItem *_statusbar_news_item; const NewsItem *GetStatusbarNews();
void DeleteInvalidEngineNews(); void DeleteInvalidEngineNews();
void DeleteVehicleNews(VehicleID vid, StringID news); void DeleteVehicleNews(VehicleID vid, StringID news);

View File

@ -21,7 +21,6 @@
#include "town.h" #include "town.h"
#include "sound_func.h" #include "sound_func.h"
#include "string_func.h" #include "string_func.h"
#include "dropdown_func.h"
#include "statusbar_gui.h" #include "statusbar_gui.h"
#include "company_manager_face.h" #include "company_manager_face.h"
#include "company_func.h" #include "company_func.h"
@ -44,25 +43,42 @@
#include "safeguards.h" #include "safeguards.h"
const NewsItem *_statusbar_news_item = nullptr; static const uint MIN_NEWS_AMOUNT = 30; ///< preferred minimum amount of news messages.
static const uint MAX_NEWS_AMOUNT = 1U << 10; ///< Do not exceed this number of news messages.
static uint MIN_NEWS_AMOUNT = 30; ///< preferred minimum amount of news messages static NewsContainer _news; ///< List of news, with newest items at the start.
static uint MAX_NEWS_AMOUNT = 1 << 10; ///< Do not exceed this number of news messages
static uint _total_news = 0; ///< current number of news items
static NewsItem *_oldest_news = nullptr; ///< head of news items queue
NewsItem *_latest_news = nullptr; ///< 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, a pointer is stored * If the message being shown was forced by the user, an iterater is stored
* in _forced_news. Otherwise, \a _forced_news variable is nullptr. * in _forced_news. Otherwise, \a _forced_news variable is the end of \a _news.
*/ */
static const NewsItem *_forced_news = nullptr; static NewsIterator _forced_news = std::end(_news);
/** Current news item (last item shown regularly). */ /** Current news item (last item shown regularly). */
static const NewsItem *_current_news = nullptr; static NewsIterator _current_news = std::end(_news);
/** Current status bar news item. */
static NewsIterator _statusbar_news = std::end(_news);
/**
* Get pointer to the current status bar news item.
* @return Pointer to the current status bar news item, or nullptr if there is none.
*/
const NewsItem *GetStatusbarNews()
{
return (_statusbar_news == std::end(_news)) ? nullptr : &*_statusbar_news;
}
/**
* Get read-only reference to all news items.
* @return Read-only reference to all news items.
*/
const NewsContainer &GetNews()
{
return _news;
}
/** /**
* Get the position a news-reference is referencing. * Get the position a news-reference is referencing.
@ -484,7 +500,7 @@ struct NewsWindow : Window {
case WID_N_CLOSEBOX: case WID_N_CLOSEBOX:
NewsWindow::duration = 0; NewsWindow::duration = 0;
this->Close(); this->Close();
_forced_news = nullptr; _forced_news = std::end(_news);
break; break;
case WID_N_CAPTION: case WID_N_CAPTION:
@ -622,29 +638,21 @@ static void ShowNewspaper(const NewsItem *ni)
} }
/** Show news item in the ticker */ /** Show news item in the ticker */
static void ShowTicker(const NewsItem *ni) static void ShowTicker(NewsIterator ni)
{ {
if (_settings_client.sound.news_ticker) SndPlayFx(SND_16_NEWS_TICKER); if (_settings_client.sound.news_ticker) SndPlayFx(SND_16_NEWS_TICKER);
_statusbar_news_item = ni; _statusbar_news = ni;
InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_TICKER); InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_TICKER);
} }
/** Initialize the news-items data structures */ /** Initialize the news-items data structures */
void InitNewsItemStructs() void InitNewsItemStructs()
{ {
for (NewsItem *ni = _oldest_news; ni != nullptr; ) { _news.clear();
NewsItem *next = ni->next; _forced_news = std::end(_news);
delete ni; _current_news = std::end(_news);
ni = next; _statusbar_news = std::end(_news);
}
_total_news = 0;
_oldest_news = nullptr;
_latest_news = nullptr;
_forced_news = nullptr;
_current_news = nullptr;
_statusbar_news_item = nullptr;
NewsWindow::duration = 0; NewsWindow::duration = 0;
} }
@ -654,7 +662,7 @@ void InitNewsItemStructs()
*/ */
static bool ReadyForNextTickerItem() static bool ReadyForNextTickerItem()
{ {
const NewsItem *ni = _statusbar_news_item; const NewsItem *ni = GetStatusbarNews();
if (ni == nullptr) return true; if (ni == nullptr) return true;
/* Ticker message /* Ticker message
@ -668,8 +676,7 @@ static bool ReadyForNextTickerItem()
*/ */
static bool ReadyForNextNewsItem() static bool ReadyForNextNewsItem()
{ {
const NewsItem *ni = _forced_news == nullptr ? _current_news : _forced_news; if (_forced_news == std::end(_news) && _current_news == std::end(_news)) return true;
if (ni == nullptr) return true;
/* neither newsticker nor newspaper are running */ /* neither newsticker nor newspaper are running */
return (NewsWindow::duration <= 0 || FindWindowById(WC_NEWS_WINDOW, 0) == nullptr); return (NewsWindow::duration <= 0 || FindWindowById(WC_NEWS_WINDOW, 0) == nullptr);
@ -678,116 +685,98 @@ static bool ReadyForNextNewsItem()
/** Move to the next ticker item */ /** Move to the next ticker item */
static void MoveToNextTickerItem() static void MoveToNextTickerItem()
{ {
assert(!std::empty(_news));
/* There is no status bar, so no reason to show news; /* There is no status bar, so no reason to show news;
* especially important with the end game screen when * especially important with the end game screen when
* there is no status bar but possible news. */ * there is no status bar but possible news. */
if (FindWindowById(WC_STATUS_BAR, 0) == nullptr) return; if (FindWindowById(WC_STATUS_BAR, 0) == nullptr) return;
/* if we're not at the last item, then move on */ /* if we're not at the latest item, then move on */
while (_statusbar_news_item != _latest_news) { while (_statusbar_news != std::begin(_news)) {
_statusbar_news_item = (_statusbar_news_item == nullptr) ? _oldest_news : _statusbar_news_item->next; --_statusbar_news;
const NewsItem *ni = _statusbar_news_item; const NewsType type = _statusbar_news->type;
const NewsType type = ni->type;
/* check the date, don't show too old items */ /* check the date, don't show too old items */
if (TimerGameEconomy::date - _news_type_data[type].age > ni->economy_date) continue; if (TimerGameEconomy::date - _news_type_data[type].age > _statusbar_news->economy_date) continue;
switch (_news_type_data[type].GetDisplay()) { switch (_news_type_data[type].GetDisplay()) {
default: NOT_REACHED(); default: NOT_REACHED();
case ND_OFF: // Off - show nothing only a small reminder in the status bar case ND_OFF: // Off - show nothing only a small reminder in the status bar
InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_REMINDER); InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_REMINDER);
break; return;
case ND_SUMMARY: // Summary - show ticker case ND_SUMMARY: // Summary - show ticker
ShowTicker(ni); ShowTicker(_statusbar_news);
break; return;
case ND_FULL: // Full - show newspaper, skipped here case ND_FULL: // Full - show newspaper, skipped here
continue; break;;
} }
return;
} }
} }
/** Move to the next news item */ /** Move to the next news item */
static void MoveToNextNewsItem() static void MoveToNextNewsItem()
{ {
assert(!std::empty(_news));
/* There is no status bar, so no reason to show news; /* There is no status bar, so no reason to show news;
* especially important with the end game screen when * especially important with the end game screen when
* there is no status bar but possible news. */ * there is no status bar but possible news. */
if (FindWindowById(WC_STATUS_BAR, 0) == nullptr) return; if (FindWindowById(WC_STATUS_BAR, 0) == nullptr) return;
CloseWindowById(WC_NEWS_WINDOW, 0); // close the newspapers window if shown CloseWindowById(WC_NEWS_WINDOW, 0); // close the newspapers window if shown
_forced_news = nullptr; _forced_news = std::end(_news);
/* if we're not at the last item, then move on */ /* if we're not at the latest item, then move on */
while (_current_news != _latest_news) { while (_current_news != std::begin(_news)) {
_current_news = (_current_news == nullptr) ? _oldest_news : _current_news->next; --_current_news;
const NewsItem *ni = _current_news; const NewsType type = _current_news->type;
const NewsType type = ni->type;
/* check the date, don't show too old items */ /* check the date, don't show too old items */
if (TimerGameEconomy::date - _news_type_data[type].age > ni->economy_date) continue; if (TimerGameEconomy::date - _news_type_data[type].age > _current_news->economy_date) continue;
switch (_news_type_data[type].GetDisplay()) { switch (_news_type_data[type].GetDisplay()) {
default: NOT_REACHED(); default: NOT_REACHED();
case ND_OFF: // Off - show nothing only a small reminder in the status bar, skipped here case ND_OFF: // Off - show nothing only a small reminder in the status bar, skipped here
continue; break;
case ND_SUMMARY: // Summary - show ticker, skipped here case ND_SUMMARY: // Summary - show ticker, skipped here
continue; break;;
case ND_FULL: // Full - show newspaper case ND_FULL: // Full - show newspaper
ShowNewspaper(ni); ShowNewspaper(&*_current_news);
break; return;
} }
return;
} }
} }
/** Delete a news item from the queue */ /** Delete a news item from the queue */
static void DeleteNewsItem(NewsItem *ni) static std::list<NewsItem>::iterator DeleteNewsItem(std::list<NewsItem>::iterator ni)
{ {
/* Delete the news from the news queue. */
if (ni->prev != nullptr) {
ni->prev->next = ni->next;
} else {
assert(_oldest_news == ni);
_oldest_news = ni->next;
}
if (ni->next != nullptr) {
ni->next->prev = ni->prev;
} else {
assert(_latest_news == ni);
_latest_news = ni->prev;
}
_total_news--;
if (_forced_news == ni || _current_news == ni) { if (_forced_news == ni || _current_news == ni) {
/* When we're the current news, go to the previous item first; /* When we're the current news, go to the previous item first;
* we just possibly made that the last news item. */ * we just possibly made that the last news item. */
if (_current_news == ni) _current_news = ni->prev; if (_current_news == ni) _current_news = (_current_news == std::begin(_news)) ? std::end(_news) : std::prev(_current_news);
/* About to remove the currently forced item (shown as newspapers) || /* About to remove the currently forced item (shown as newspapers) ||
* about to remove the currently displayed item (newspapers) */ * about to remove the currently displayed item (newspapers) */
MoveToNextNewsItem(); MoveToNextNewsItem();
} }
if (_statusbar_news_item == ni) { if (_statusbar_news == ni) {
/* When we're the current news, go to the previous item first; /* When we're the current news, go to the previous item first;
* we just possibly made that the last news item. */ * we just possibly made that the last news item. */
_statusbar_news_item = ni->prev; if (_statusbar_news == ni) _statusbar_news = (_statusbar_news == std::begin(_news)) ? std::end(_news) : std::prev(_statusbar_news);
/* About to remove the currently displayed item (ticker, or just a reminder) */ /* About to remove the currently displayed item (ticker, or just a reminder) */
InvalidateWindowData(WC_STATUS_BAR, 0, SBI_NEWS_DELETED); // invalidate the statusbar InvalidateWindowData(WC_STATUS_BAR, 0, SBI_NEWS_DELETED); // invalidate the statusbar
MoveToNextTickerItem(); MoveToNextTickerItem();
} }
delete ni; /* Delete the news from the news queue. */
return _news.erase(ni);
SetWindowDirty(WC_MESSAGE_HISTORY, 0);
} }
/** /**
@ -829,27 +818,14 @@ void AddNewsItem(StringID string, NewsType type, NewsFlag flags, NewsReferenceTy
if (_game_mode == GM_MENU) return; if (_game_mode == GM_MENU) return;
/* Create new news item node */ /* Create new news item node */
NewsItem *ni = new NewsItem(string, type, flags, reftype1, ref1, reftype2, ref2, data); _news.emplace_front(string, type, flags, reftype1, ref1, reftype2, ref2, data);
if (_total_news++ == 0) {
assert(_oldest_news == nullptr);
_oldest_news = ni;
ni->prev = nullptr;
} else {
assert(_latest_news->next == nullptr);
_latest_news->next = ni;
ni->prev = _latest_news;
}
ni->next = nullptr;
_latest_news = ni;
/* Keep the number of stored news items to a managable number */ /* Keep the number of stored news items to a managable number */
if (_total_news > MAX_NEWS_AMOUNT) { if (std::size(_news) > MAX_NEWS_AMOUNT) {
DeleteNewsItem(_oldest_news); DeleteNewsItem(std::prev(std::end(_news)));
} }
SetWindowDirty(WC_MESSAGE_HISTORY, 0); InvalidateWindowData(WC_MESSAGE_HISTORY, 0);
} }
/** /**
@ -910,6 +886,29 @@ CommandCost CmdCustomNewsItem(DoCommandFlag flags, NewsType type, NewsReferenceT
return CommandCost(); return CommandCost();
} }
/**
* Delete news items by predicate, and invalidate the message history if necessary.
* @tparam Tmin Stop if the number of news items remaining reaches \a min items.
* @tparam Tpredicate Condition for a news item to be deleted.
*/
template <size_t Tmin = 0, class Tpredicate>
void DeleteNews(Tpredicate predicate)
{
bool dirty = false;
for (auto it = std::rbegin(_news); it != std::rend(_news); /* nothing */) {
if constexpr (Tmin > 0) {
if (std::size(_news) <= Tmin) break;
}
if (predicate(*it)) {
it = std::make_reverse_iterator(DeleteNewsItem(std::prev(it.base())));
dirty = true;
} else {
++it;
}
}
if (dirty) InvalidateWindowData(WC_MESSAGE_HISTORY, 0);
}
/** /**
* Delete a news item type about a vehicle. * Delete a news item type about a vehicle.
* When the news item type is INVALID_STRING_ID all news about the vehicle gets deleted. * When the news item type is INVALID_STRING_ID all news about the vehicle gets deleted.
@ -918,16 +917,9 @@ CommandCost CmdCustomNewsItem(DoCommandFlag flags, NewsType type, NewsReferenceT
*/ */
void DeleteVehicleNews(VehicleID vid, StringID news) void DeleteVehicleNews(VehicleID vid, StringID news)
{ {
NewsItem *ni = _oldest_news; DeleteNews([&](const auto &ni) {
return ((ni.reftype1 == NR_VEHICLE && ni.ref1 == vid) || (ni.reftype2 == NR_VEHICLE && ni.ref2 == vid)) && (news == INVALID_STRING_ID || ni.string_id == news);
while (ni != nullptr) { });
NewsItem *next = ni->next;
if (((ni->reftype1 == NR_VEHICLE && ni->ref1 == vid) || (ni->reftype2 == NR_VEHICLE && ni->ref2 == vid)) &&
(news == INVALID_STRING_ID || ni->string_id == news)) {
DeleteNewsItem(ni);
}
ni = next;
}
} }
/** /**
@ -937,15 +929,9 @@ void DeleteVehicleNews(VehicleID vid, StringID news)
*/ */
void DeleteStationNews(StationID sid) void DeleteStationNews(StationID sid)
{ {
NewsItem *ni = _oldest_news; DeleteNews([&](const auto &ni) {
return (ni.reftype1 == NR_STATION && ni.ref1 == sid) || (ni.reftype2 == NR_STATION && ni.ref2 == sid);
while (ni != nullptr) { });
NewsItem *next = ni->next;
if ((ni->reftype1 == NR_STATION && ni->ref1 == sid) || (ni->reftype2 == NR_STATION && ni->ref2 == sid)) {
DeleteNewsItem(ni);
}
ni = next;
}
} }
/** /**
@ -954,15 +940,9 @@ void DeleteStationNews(StationID sid)
*/ */
void DeleteIndustryNews(IndustryID iid) void DeleteIndustryNews(IndustryID iid)
{ {
NewsItem *ni = _oldest_news; DeleteNews([&](const auto &ni) {
return (ni.reftype1 == NR_INDUSTRY && ni.ref1 == iid) || (ni.reftype2 == NR_INDUSTRY && ni.ref2 == iid);
while (ni != nullptr) { });
NewsItem *next = ni->next;
if ((ni->reftype1 == NR_INDUSTRY && ni->ref1 == iid) || (ni->reftype2 == NR_INDUSTRY && ni->ref2 == iid)) {
DeleteNewsItem(ni);
}
ni = next;
}
} }
/** /**
@ -970,25 +950,17 @@ void DeleteIndustryNews(IndustryID iid)
*/ */
void DeleteInvalidEngineNews() void DeleteInvalidEngineNews()
{ {
NewsItem *ni = _oldest_news; DeleteNews([](const auto &ni) {
return (ni.reftype1 == NR_ENGINE && (!Engine::IsValidID(ni.ref1) || !Engine::Get(ni.ref1)->IsEnabled())) ||
while (ni != nullptr) { (ni.reftype2 == NR_ENGINE && (!Engine::IsValidID(ni.ref2) || !Engine::Get(ni.ref2)->IsEnabled()));
NewsItem *next = ni->next; });
if ((ni->reftype1 == NR_ENGINE && (!Engine::IsValidID(ni->ref1) || !Engine::Get(ni->ref1)->IsEnabled())) ||
(ni->reftype2 == NR_ENGINE && (!Engine::IsValidID(ni->ref2) || !Engine::Get(ni->ref2)->IsEnabled()))) {
DeleteNewsItem(ni);
}
ni = next;
}
} }
static void RemoveOldNewsItems() static void RemoveOldNewsItems()
{ {
NewsItem *next; DeleteNews<MIN_NEWS_AMOUNT>([](const auto &ni) {
for (NewsItem *cur = _oldest_news; _total_news > MIN_NEWS_AMOUNT && cur != nullptr; cur = next) { return TimerGameEconomy::date - _news_type_data[ni.type].age * _settings_client.gui.news_message_timeout > ni.economy_date;
next = cur->next; });
if (TimerGameEconomy::date - _news_type_data[cur->type].age * _settings_client.gui.news_message_timeout > cur->economy_date) DeleteNewsItem(cur);
}
} }
/** /**
@ -999,17 +971,17 @@ static void RemoveOldNewsItems()
*/ */
void ChangeVehicleNews(VehicleID from_index, VehicleID to_index) void ChangeVehicleNews(VehicleID from_index, VehicleID to_index)
{ {
for (NewsItem *ni = _oldest_news; ni != nullptr; ni = ni->next) { for (auto &ni : _news) {
if (ni->reftype1 == NR_VEHICLE && ni->ref1 == from_index) ni->ref1 = to_index; if (ni.reftype1 == NR_VEHICLE && ni.ref1 == from_index) ni.ref1 = to_index;
if (ni->reftype2 == NR_VEHICLE && ni->ref2 == from_index) ni->ref2 = to_index; if (ni.reftype2 == NR_VEHICLE && ni.ref2 == from_index) ni.ref2 = to_index;
if (ni->flags & NF_VEHICLE_PARAM0 && ni->params[0].data == from_index) ni->params[0] = to_index; if (ni.flags & NF_VEHICLE_PARAM0 && ni.params[0].data == from_index) ni.params[0] = to_index;
} }
} }
void NewsLoop() void NewsLoop()
{ {
/* no news item yet */ /* no news item yet */
if (_total_news == 0) return; if (std::empty(_news)) return;
static TimerGameEconomy::Month _last_clean_month = 0; static TimerGameEconomy::Month _last_clean_month = 0;
@ -1023,9 +995,9 @@ void NewsLoop()
} }
/** Do a forced show of a specific message */ /** Do a forced show of a specific message */
static void ShowNewsMessage(const NewsItem *ni) static void ShowNewsMessage(NewsIterator ni)
{ {
assert(_total_news != 0); assert(!std::empty(_news));
/* Delete the news window */ /* Delete the news window */
CloseWindowById(WC_NEWS_WINDOW, 0); CloseWindowById(WC_NEWS_WINDOW, 0);
@ -1033,9 +1005,9 @@ static void ShowNewsMessage(const NewsItem *ni)
/* setup forced news item */ /* setup forced news item */
_forced_news = ni; _forced_news = ni;
if (_forced_news != nullptr) { if (_forced_news != std::end(_news)) {
CloseWindowById(WC_NEWS_WINDOW, 0); CloseWindowById(WC_NEWS_WINDOW, 0);
ShowNewspaper(ni); ShowNewspaper(&*ni);
} }
} }
@ -1054,26 +1026,26 @@ bool HideActiveNewsMessage()
/** Show previous news item */ /** Show previous news item */
void ShowLastNewsMessage() void ShowLastNewsMessage()
{ {
const NewsItem *ni = nullptr; if (std::empty(_news)) return;
if (_total_news == 0) {
return; NewsIterator ni;
} else if (_forced_news == nullptr) { if (_forced_news == std::end(_news)) {
/* 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 */
if (_current_news == nullptr) { if (_current_news == std::end(_news)) {
/* No news were shown yet resp. the last shown one was already deleted. /* No news were shown yet resp. the last shown one was already deleted.
* Threat this as if _forced_news reached _oldest_news; so, wrap around and start anew with the latest. */ * Treat this as if _forced_news reached the oldest news; so, wrap around and start anew with the latest. */
ni = _latest_news; ni = std::begin(_news);
} else { } else {
const Window *w = FindWindowById(WC_NEWS_WINDOW, 0); const Window *w = FindWindowById(WC_NEWS_WINDOW, 0);
ni = (w == nullptr || (_current_news == _oldest_news)) ? _current_news : _current_news->prev; ni = (w == nullptr || (std::next(_current_news) == std::end(_news))) ? _current_news : std::next(_current_news);
} }
} else if (_forced_news == _oldest_news) { } else if (std::next(_forced_news) == std::end(_news)) {
/* We have reached the oldest news, start anew with the latest */ /* We have reached the oldest news, start anew with the latest */
ni = _latest_news; ni = std::begin(_news);
} else { } else {
/* 'Scrolling' through news history show each one in turn */ /* 'Scrolling' through news history show each one in turn */
ni = _forced_news->prev; ni = std::next(_forced_news);
} }
bool wrap = false; bool wrap = false;
for (;;) { for (;;) {
@ -1082,11 +1054,11 @@ void ShowLastNewsMessage()
break; break;
} }
ni = ni->prev; ++ni;
if (ni == nullptr) { if (ni == std::end(_news)) {
if (wrap) break; if (wrap) break;
/* We have reached the oldest news, start anew with the latest */ /* We have reached the oldest news, start anew with the latest */
ni = _latest_news; ni = std::begin(_news);
wrap = true; wrap = true;
} }
} }
@ -1143,37 +1115,23 @@ struct MessageHistoryWindow : Window {
} }
} }
void OnPaint() override
{
this->OnInvalidateData(0);
this->DrawWidgets();
}
void DrawWidget(const Rect &r, WidgetID widget) const override void DrawWidget(const Rect &r, WidgetID widget) const override
{ {
if (widget != WID_MH_BACKGROUND || _total_news == 0) return; if (widget != WID_MH_BACKGROUND || std::empty(_news)) return;
/* Find the first news item to display. */
NewsItem *ni = _latest_news;
for (int n = this->vscroll->GetPosition(); n > 0; n--) {
ni = ni->prev;
if (ni == nullptr) return;
}
/* Fill the widget with news items. */ /* Fill the widget with news items. */
bool rtl = _current_text_dir == TD_RTL; bool rtl = _current_text_dir == TD_RTL;
Rect news = r.Shrink(WidgetDimensions::scaled.framerect).Indent(this->date_width + WidgetDimensions::scaled.hsep_wide, rtl); Rect news = r.Shrink(WidgetDimensions::scaled.framerect).Indent(this->date_width + WidgetDimensions::scaled.hsep_wide, rtl);
Rect date = r.Shrink(WidgetDimensions::scaled.framerect).WithWidth(this->date_width, rtl); Rect date = r.Shrink(WidgetDimensions::scaled.framerect).WithWidth(this->date_width, rtl);
int y = news.top; int y = news.top;
for (int n = this->vscroll->GetCapacity(); n > 0; n--) {
auto [first, last] = this->vscroll->GetVisibleRangeIterators(_news);
for (auto ni = first; ni != last; ++ni) {
SetDParam(0, ni->date); SetDParam(0, ni->date);
DrawString(date.left, date.right, y, STR_JUST_DATE_TINY, TC_WHITE); DrawString(date.left, date.right, y, STR_JUST_DATE_TINY, TC_WHITE);
DrawNewsString(news.left, news.right, y, TC_WHITE, ni); DrawNewsString(news.left, news.right, y, TC_WHITE, &*ni);
y += this->line_height; y += this->line_height;
ni = ni->prev;
if (ni == nullptr) return;
} }
} }
@ -1185,19 +1143,18 @@ struct MessageHistoryWindow : Window {
void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
{ {
if (!gui_scope) return; if (!gui_scope) return;
this->vscroll->SetCount(_total_news); this->vscroll->SetCount(std::size(_news));
} }
void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
{ {
if (widget == WID_MH_BACKGROUND) { if (widget == WID_MH_BACKGROUND) {
NewsItem *ni = _latest_news; /* Scheduled window invalidations currently occur after the input loop, which means the scrollbar count
if (ni == nullptr) return; * could be invalid, so ensure it's correct now. Potentially this means that item clicked on might be
* different as well. */
for (int n = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_MH_BACKGROUND, WidgetDimensions::scaled.framerect.top); n > 0; n--) { this->vscroll->SetCount(std::size(_news));
ni = ni->prev; auto ni = this->vscroll->GetScrolledItemFromWidget(_news, pt.y, this, widget);
if (ni == nullptr) return; if (ni == std::end(_news)) return;
}
ShowNewsMessage(ni); ShowNewsMessage(ni);
} }

View File

@ -16,6 +16,6 @@ void ShowLastNewsMessage();
void ShowMessageHistory(); void ShowMessageHistory();
bool HideActiveNewsMessage(); bool HideActiveNewsMessage();
extern NewsItem *_latest_news; const NewsContainer &GetNews();
#endif /* NEWS_GUI_H */ #endif /* NEWS_GUI_H */

View File

@ -126,8 +126,6 @@ struct NewsAllocatedData {
/** Information about a single item of news. */ /** Information about a single item of news. */
struct NewsItem { struct NewsItem {
NewsItem *prev; ///< Previous news item
NewsItem *next; ///< Next news item
StringID string_id; ///< Message text StringID string_id; ///< Message text
TimerGameCalendar::Date date; ///< Calendar date to show for the news TimerGameCalendar::Date date; ///< Calendar date to show for the news
TimerGameEconomy::Date economy_date; ///< Economy date of the news item, never shown but used to calculate age TimerGameEconomy::Date economy_date; ///< Economy date of the news item, never shown but used to calculate age
@ -169,4 +167,7 @@ struct CompanyNewsInformation : NewsAllocatedData {
CompanyNewsInformation(const struct Company *c, const struct Company *other = nullptr); CompanyNewsInformation(const struct Company *c, const struct Company *other = nullptr);
}; };
using NewsContainer = std::list<NewsItem>; ///< Container type for storing news items.
using NewsIterator = NewsContainer::const_iterator; ///< Iterator type for news items.
#endif /* NEWS_TYPE_H */ #endif /* NEWS_TYPE_H */

View File

@ -145,9 +145,9 @@ struct StatusBarWindow : Window {
} else if (_pause_mode != PM_UNPAUSED) { } else if (_pause_mode != PM_UNPAUSED) {
StringID msg = (_pause_mode & PM_PAUSED_LINK_GRAPH) ? STR_STATUSBAR_PAUSED_LINK_GRAPH : STR_STATUSBAR_PAUSED; StringID msg = (_pause_mode & PM_PAUSED_LINK_GRAPH) ? STR_STATUSBAR_PAUSED_LINK_GRAPH : STR_STATUSBAR_PAUSED;
DrawString(tr, msg, TC_FROMSTRING, SA_HOR_CENTER); DrawString(tr, msg, TC_FROMSTRING, SA_HOR_CENTER);
} else if (this->ticker_scroll < TICKER_STOP && _statusbar_news_item != nullptr && _statusbar_news_item->string_id != 0) { } else if (this->ticker_scroll < TICKER_STOP && GetStatusbarNews() != nullptr && GetStatusbarNews()->string_id != 0) {
/* Draw the scrolling news text */ /* Draw the scrolling news text */
if (!DrawScrollingStatusText(_statusbar_news_item, ScaleGUITrad(this->ticker_scroll), tr.left, tr.right, tr.top, tr.bottom)) { if (!DrawScrollingStatusText(GetStatusbarNews(), ScaleGUITrad(this->ticker_scroll), tr.left, tr.right, tr.top, tr.bottom)) {
InvalidateWindowData(WC_STATUS_BAR, 0, SBI_NEWS_DELETED); InvalidateWindowData(WC_STATUS_BAR, 0, SBI_NEWS_DELETED);
if (Company::IsValidID(_local_company)) { if (Company::IsValidID(_local_company)) {
/* This is the default text */ /* This is the default text */