# news_gui.cpp

Go to the documentation of this file.

    00001 /* $Id: news_gui.cpp 15725 2009-03-15 15:25:18Z smatz $ */
    00002
    00005 #include "stdafx.h"
    00006 #include "openttd.h"
    00007 #include "gui.h"
    00008 #include "window_gui.h"
    00009 #include "viewport_func.h"
    00010 #include "news_type.h"
    00011 #include "settings_type.h"
    00012 #include "transparency.h"
    00013 #include "strings_func.h"
    00014 #include "window_func.h"
    00015 #include "date_func.h"
    00016 #include "vehicle_base.h"
    00017 #include "sound_func.h"
    00018 #include "string_func.h"
    00019 #include "widgets/dropdown_func.h"
    00020 #include "statusbar_gui.h"
    00021 #include "company_manager_face.h"
    00022
    00023 #include "table/strings.h"
    00024
    00025 #define NB_WIDG_PER_SETTING 4
    00026
    00027 NewsItem _statusbar_news_item;
    00028 bool _news_ticker_sound;
    00029
    00030 static uint MIN_NEWS_AMOUNT = 30;
    00031 static uint _total_news = 0;
    00032 static NewsItem *_oldest_news = NULL;
    00033 static NewsItem *_latest_news = NULL;
    00034
    00039 static NewsItem *_forced_news = NULL;
    00040
    00042 static NewsItem *_current_news = NULL;
    00043
    00044
    00045 typedef void DrawNewsCallbackProc(struct Window *w, const NewsItem *ni);
    00046 void DrawNewsNewVehicleAvail(Window *w, const NewsItem *ni);
    00047
    00048 static void DrawNewsBankrupcy(Window *w, const NewsItem *ni)
    00049 {
    00050   const CompanyNewsInformation *cni = (const CompanyNewsInformation*)ni->free_data;
    00051
    00052   DrawCompanyManagerFace(cni->face, cni->colour, 2, 23);
    00053   GfxFillRect(3, 23, 3 + 91, 23 + 118, PALETTE_TO_STRUCT_GREY, FILLRECT_RECOLOUR);
    00054
    00055   SetDParamStr(0, cni->president_name);
    00056   DrawStringMultiCenter(49, 148, STR_JUST_RAW_STRING, 94);
    00057
    00058   switch (ni->subtype) {
    00059     case NS_COMPANY_TROUBLE:
    00060       DrawStringCentered(w->width >> 1, 1, STR_7056_TRANSPORT_COMPANY_IN_TROUBLE, TC_FROMSTRING);
    00061
    00062       SetDParam(0, ni->params[2]);
    00063
    00064       DrawStringMultiCenter(
    00065         ((w->width - 101) >> 1) + 98,
    00066         90,
    00067         STR_7057_WILL_BE_SOLD_OFF_OR_DECLARED,
    00068         w->width - 101);
    00069       break;
    00070
    00071     case NS_COMPANY_MERGER:
    00072       DrawStringCentered(w->width >> 1, 1, STR_7059_TRANSPORT_COMPANY_MERGER, TC_FROMSTRING);
    00073       SetDParam(0, ni->params[2]);
    00074       SetDParam(1, ni->params[3]);
    00075       SetDParam(2, ni->params[4]);
    00076       DrawStringMultiCenter(
    00077         ((w->width - 101) >> 1) + 98,
    00078         90,
    00079         ni->params[4] == 0 ? STR_707F_HAS_BEEN_TAKEN_OVER_BY : STR_705A_HAS_BEEN_SOLD_TO_FOR,
    00080         w->width - 101);
    00081       break;
    00082
    00083     case NS_COMPANY_BANKRUPT:
    00084       DrawStringCentered(w->width >> 1, 1, STR_705C_BANKRUPT, TC_FROMSTRING);
    00085       SetDParam(0, ni->params[2]);
    00086       DrawStringMultiCenter(
    00087         ((w->width - 101) >> 1) + 98,
    00088         90,
    00089         STR_705D_HAS_BEEN_CLOSED_DOWN_BY,
    00090         w->width - 101);
    00091       break;
    00092
    00093     case NS_COMPANY_NEW:
    00094       DrawStringCentered(w->width >> 1, 1, STR_705E_NEW_TRANSPORT_COMPANY_LAUNCHED, TC_FROMSTRING);
    00095       SetDParam(0, ni->params[2]);
    00096       SetDParam(1, ni->params[3]);
    00097       DrawStringMultiCenter(
    00098         ((w->width - 101) >> 1) + 98,
    00099         90,
    00100         STR_705F_STARTS_CONSTRUCTION_NEAR,
    00101         w->width - 101);
    00102       break;
    00103
    00104     default:
    00105       NOT_REACHED();
    00106   }
    00107 }
    00108
    00109
    00113 struct NewsSubtypeData {
    00114   NewsType type;
    00115   NewsMode display_mode;
    00116   NewsFlag flags;
    00117   DrawNewsCallbackProc *callback;
    00118 };
    00119
    00123 static const struct NewsSubtypeData _news_subtype_data[NS_END] = {
    00124   /* type,               display_mode, flags,                  callback */
    00125   { NT_ARRIVAL_COMPANY,  NM_THIN,     NF_VIEWPORT|NF_VEHICLE, NULL                    },
    00126   { NT_ARRIVAL_OTHER,    NM_THIN,     NF_VIEWPORT|NF_VEHICLE, NULL                    },
    00127   { NT_ACCIDENT,         NM_THIN,     NF_VIEWPORT|NF_TILE,    NULL                    },
    00128   { NT_ACCIDENT,         NM_THIN,     NF_VIEWPORT|NF_VEHICLE, NULL                    },
    00129   { NT_COMPANY_INFO,     NM_NORMAL,   NF_NONE,                DrawNewsBankrupcy       },
    00130   { NT_COMPANY_INFO,     NM_NORMAL,   NF_NONE,                DrawNewsBankrupcy       },
    00131   { NT_COMPANY_INFO,     NM_NORMAL,   NF_NONE,                DrawNewsBankrupcy       },
    00132   { NT_COMPANY_INFO,     NM_NORMAL,   NF_TILE,                DrawNewsBankrupcy       },
    00133   { NT_INDUSTRY_OPEN,    NM_THIN,     NF_VIEWPORT|NF_TILE,    NULL                    },
    00134   { NT_INDUSTRY_CLOSE,   NM_THIN,     NF_VIEWPORT|NF_TILE,    NULL                    },
    00135   { NT_ECONOMY,          NM_NORMAL,   NF_NONE,                NULL                    },
    00136   { NT_INDUSTRY_COMPANY, NM_THIN,     NF_VIEWPORT|NF_TILE,    NULL                    },
    00137   { NT_INDUSTRY_OTHER,   NM_THIN,     NF_VIEWPORT|NF_TILE,    NULL                    },
    00138   { NT_INDUSTRY_NOBODY,  NM_THIN,     NF_VIEWPORT|NF_TILE,    NULL                    },
    00139   { NT_ADVICE,           NM_SMALL,    NF_VIEWPORT|NF_VEHICLE, NULL                    },
    00140   { NT_NEW_VEHICLES,     NM_NORMAL,   NF_NONE,                DrawNewsNewVehicleAvail },
    00141   { NT_ACCEPTANCE,       NM_SMALL,    NF_VIEWPORT|NF_TILE,    NULL                    },
    00142   { NT_SUBSIDIES,        NM_NORMAL,   NF_TILE|NF_TILE2,       NULL                    },
    00143   { NT_GENERAL,          NM_NORMAL,   NF_TILE,                NULL                    },
    00144 };
    00145
    00149 NewsTypeData _news_type_data[NT_END] = {
    00150   /* name,              age, sound,           display */
    00151   { "arrival_player",    60, SND_1D_APPLAUSE, ND_FULL },
    00152   { "arrival_other",     60, SND_1D_APPLAUSE, ND_FULL },
    00153   { "accident",          90, SND_BEGIN,       ND_FULL },
    00154   { "company_info",      60, SND_BEGIN,       ND_FULL },
    00155   { "open",              90, SND_BEGIN,       ND_FULL },
    00156   { "close",             90, SND_BEGIN,       ND_FULL },
    00157   { "economy",           30, SND_BEGIN,       ND_FULL },
    00158   { "production_player", 30, SND_BEGIN,       ND_FULL },
    00159   { "production_other",  30, SND_BEGIN,       ND_FULL },
    00160   { "production_nobody", 30, SND_BEGIN,       ND_FULL },
    00161   { "advice",           150, SND_BEGIN,       ND_FULL },
    00162   { "new_vehicles",      30, SND_1E_OOOOH,    ND_FULL },
    00163   { "acceptance",        90, SND_BEGIN,       ND_FULL },
    00164   { "subsidies",        180, SND_BEGIN,       ND_FULL },
    00165   { "general",           60, SND_BEGIN,       ND_FULL },
    00166 };
    00167
    00168 struct NewsWindow : Window {
    00169   uint16 chat_height;
    00170   NewsItem *ni;
    00171   static uint duration;
    00172
    00173   NewsWindow(const WindowDesc *desc, NewsItem *ni) : Window(desc), ni(ni)
    00174   {
    00175     NewsWindow::duration = 555;
    00176     const Window *w = FindWindowById(WC_SEND_NETWORK_MSG, 0);
    00177     this->chat_height = (w != NULL) ? w->height : 0;
    00178
    00179     this->ni = _forced_news == NULL ? _current_news : _forced_news;
    00180     this->flags4 |= WF_DISABLE_VP_SCROLL;
    00181
    00182     this->FindWindowPlacementAndResize(desc);
    00183   }
    00184
    00185   void DrawNewsBorder()
    00186   {
    00187     int left = 0;
    00188     int right = this->width - 1;
    00189     int top = 0;
    00190     int bottom = this->height - 1;
    00191
    00192     GfxFillRect(left,  top,    right, bottom, 0xF);
    00193
    00194     GfxFillRect(left,  top,    left,  bottom, 0xD7);
    00195     GfxFillRect(right, top,    right, bottom, 0xD7);
    00196     GfxFillRect(left,  top,    right, top,    0xD7);
    00197     GfxFillRect(left,  bottom, right, bottom, 0xD7);
    00198
    00199     DrawString(left + 2, top + 1, STR_00C6, TC_FROMSTRING);
    00200   }
    00201
    00202   virtual void OnPaint()
    00203   {
    00204     const NewsMode display_mode = _news_subtype_data[this->ni->subtype].display_mode;
    00205
    00206     switch (display_mode) {
    00207       case NM_NORMAL:
    00208       case NM_THIN: {
    00209         this->DrawNewsBorder();
    00210
    00211         if (_news_subtype_data[this->ni->subtype].callback != NULL) {
    00212           (_news_subtype_data[this->ni->subtype].callback)(this, ni);
    00213           break;
    00214         }
    00215
    00216         DrawString(2, 1, STR_00C6, TC_FROMSTRING);
    00217
    00218         SetDParam(0, this->ni->date);
    00219         DrawStringRightAligned(428, 1, STR_01FF, TC_FROMSTRING);
    00220
    00221         if (!(this->ni->flags & NF_VIEWPORT)) {
    00222           CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
    00223           DrawStringMultiCenter(215, display_mode == NM_NORMAL ? 76 : 56,
    00224             this->ni->string_id, this->width - 4);
    00225         } else {
    00226           /* Back up transparency options to draw news view */
    00227           TransparencyOptionBits to_backup = _transparency_opt;
    00228           _transparency_opt = 0;
    00229           this->DrawViewport();
    00230           _transparency_opt = to_backup;
    00231
    00232           /* Shade the viewport into gray, or colour*/
    00233           ViewPort *vp = this->viewport;
    00234           GfxFillRect(vp->left - this->left, vp->top - this->top,
    00235             vp->left - this->left + vp->width - 1, vp->top - this->top + vp->height - 1,
    00236             (this->ni->flags & NF_INCOLOUR ? PALETTE_TO_TRANSPARENT : PALETTE_TO_STRUCT_GREY), FILLRECT_RECOLOUR
    00237           );
    00238
    00239           CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
    00240           DrawStringMultiCenter(this->width / 2, 20, this->ni->string_id, this->width - 4);
    00241         }
    00242         break;
    00243       }
    00244
    00245       default:
    00246         this->DrawWidgets();
    00247         if (!(this->ni->flags & NF_VIEWPORT)) {
    00248           CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
    00249           DrawStringMultiCenter(140, 38, this->ni->string_id, 276);
    00250         } else {
    00251           this->DrawViewport();
    00252           CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
    00253           DrawStringMultiCenter(this->width / 2, this->height - 16, this->ni->string_id, this->width - 4);
    00254         }
    00255         break;
    00256     }
    00257   }
    00258
    00259   virtual void OnClick(Point pt, int widget)
    00260   {
    00261     switch (widget) {
    00262       case 1:
    00263         NewsWindow::duration = 0;
    00264         delete this;
    00265         _forced_news = NULL;
    00266         break;
    00267
    00268       case 0:
    00269         if (this->ni->flags & NF_VEHICLE) {
    00270           const Vehicle *v = GetVehicle(this->ni->data_a);
    00271           ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos);
    00272         } else if (this->ni->flags & NF_TILE) {
    00273           if (_ctrl_pressed) {
    00274             ShowExtraViewPortWindow(this->ni->data_a);
    00275             if (this->ni->flags & NF_TILE2) {
    00276               ShowExtraViewPortWindow(this->ni->data_b);
    00277             }
    00278           } else {
    00279             if (!ScrollMainWindowToTile(this->ni->data_a) && this->ni->flags & NF_TILE2) {
    00280               ScrollMainWindowToTile(this->ni->data_b);
    00281             }
    00282           }
    00283         }
    00284         break;
    00285     }
    00286   }
    00287
    00288   virtual EventState OnKeyPress(uint16 key, uint16 keycode)
    00289   {
    00290     if (keycode == WKC_SPACE) {
    00291       /* Don't continue. */
    00292       delete this;
    00293       return ES_HANDLED;
    00294     }
    00295     return ES_NOT_HANDLED;
    00296   }
    00297
    00298   virtual void OnInvalidateData(int data)
    00299   {
    00300     /* The chatbar has notified us that is was either created or closed */
    00301     this->chat_height = data;
    00302   }
    00303
    00304   virtual void OnTick()
    00305   {
    00306     /* Scroll up newsmessages from the bottom in steps of 4 pixels */
    00307     int y = max(this->top - 4, _screen.height - this->height - 12 - this->chat_height);
    00308     if (y == this->top) return;
    00309
    00310     if (this->viewport != NULL) this->viewport->top += y - this->top;
    00311
    00312     int diff = Delta(this->top, y);
    00313     this->top = y;
    00314
    00315     SetDirtyBlocks(this->left, this->top - diff, this->left + this->width, this->top + this->height);
    00316   }
    00317 };
    00318
    00319 /* static */ uint NewsWindow::duration;
    00320
    00321
    00322 static const Widget _news_type13_widgets[] = {
    00323 {      WWT_PANEL,   RESIZE_NONE,  COLOUR_WHITE,     0,   429,     0,   169, 0x0, STR_NULL},
    00324 {      WWT_PANEL,   RESIZE_NONE,  COLOUR_WHITE,     0,    10,     0,    11, 0x0, STR_NULL},
    00325 {   WIDGETS_END},
    00326 };
    00327
    00328 static WindowDesc _news_type13_desc(
    00329   WDP_CENTER, 476, 430, 170, 430, 170,
    00330   WC_NEWS_WINDOW, WC_NONE,
    00331   WDF_DEF_WIDGET,
    00332   _news_type13_widgets
    00333 );
    00334
    00335 static const Widget _news_type2_widgets[] = {
    00336 {      WWT_PANEL,   RESIZE_NONE,  COLOUR_WHITE,     0,   429,     0,   129, 0x0, STR_NULL},
    00337 {      WWT_PANEL,   RESIZE_NONE,  COLOUR_WHITE,     0,    10,     0,    11, 0x0, STR_NULL},
    00338 {   WIDGETS_END},
    00339 };
    00340
    00341 static WindowDesc _news_type2_desc(
    00342   WDP_CENTER, 476, 430, 130, 430, 130,
    00343   WC_NEWS_WINDOW, WC_NONE,
    00344   WDF_DEF_WIDGET,
    00345   _news_type2_widgets
    00346 );
    00347
    00348 static const Widget _news_type0_widgets[] = {
    00349 {      WWT_PANEL,   RESIZE_NONE,  COLOUR_LIGHT_BLUE,     0,   279,    14,    86, 0x0,              STR_NULL},
    00350 {   WWT_CLOSEBOX,   RESIZE_NONE,  COLOUR_LIGHT_BLUE,     0,    10,     0,    13, STR_00C5,         STR_018B_CLOSE_WINDOW},
    00351 {    WWT_CAPTION,   RESIZE_NONE,  COLOUR_LIGHT_BLUE,    11,   279,     0,    13, STR_012C_MESSAGE, STR_NULL},
    00352 {      WWT_INSET,   RESIZE_NONE,  COLOUR_LIGHT_BLUE,     2,   277,    16,    64, 0x0,              STR_NULL},
    00353 {   WIDGETS_END},
    00354 };
    00355
    00356 static WindowDesc _news_type0_desc(
    00357   WDP_CENTER, 476, 280, 87, 280, 87,
    00358   WC_NEWS_WINDOW, WC_NONE,
    00359   WDF_DEF_WIDGET,
    00360   _news_type0_widgets
    00361 );
    00362
    00363
    00365 static void ShowNewspaper(NewsItem *ni)
    00366 {
    00367   SoundFx sound = _news_type_data[_news_subtype_data[ni->subtype].type].sound;
    00368   if (sound != 0) SndPlayFx(sound);
    00369
    00370   int top = _screen.height;
    00371   Window *w;
    00372   switch (_news_subtype_data[ni->subtype].display_mode) {
    00373     case NM_NORMAL:
    00374       _news_type13_desc.top = top;
    00375       w = new NewsWindow(&_news_type13_desc, ni);
    00376       if (ni->flags & NF_VIEWPORT) {
    00377         InitializeWindowViewport(w, 2, 58, 426, 110,
    00378           ni->data_a | (ni->flags & NF_VEHICLE ? 0x80000000 : 0), ZOOM_LVL_NEWS);
    00379       }
    00380       break;
    00381
    00382     case NM_THIN:
    00383       _news_type2_desc.top = top;
    00384       w = new NewsWindow(&_news_type2_desc, ni);
    00385       if (ni->flags & NF_VIEWPORT) {
    00386         InitializeWindowViewport(w, 2, 58, 426, 70,
    00387           ni->data_a | (ni->flags & NF_VEHICLE ? 0x80000000 : 0), ZOOM_LVL_NEWS);
    00388       }
    00389       break;
    00390
    00391     default:
    00392       _news_type0_desc.top = top;
    00393       w = new NewsWindow(&_news_type0_desc, ni);
    00394       if (ni->flags & NF_VIEWPORT) {
    00395         InitializeWindowViewport(w, 3, 17, 274, 47,
    00396           ni->data_a | (ni->flags & NF_VEHICLE ? 0x80000000 : 0), ZOOM_LVL_NEWS);
    00397       }
    00398       break;
    00399   }
    00400 }
    00401
    00403 static void ShowTicker(const NewsItem *ni)
    00404 {
    00405   if (_news_ticker_sound) SndPlayFx(SND_16_MORSE);
    00406
    00407   _statusbar_news_item = *ni;
    00408   InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_TICKER);
    00409 }
    00410
    00412 void InitNewsItemStructs()
    00413 {
    00414   for (NewsItem *ni = _oldest_news; ni != NULL; ) {
    00415     NewsItem *next = ni->next;
    00416     delete ni;
    00417     ni = next;
    00418   }
    00419
    00420   _total_news = 0;
    00421   _oldest_news = NULL;
    00422   _latest_news = NULL;
    00423   _forced_news = NULL;
    00424   _current_news = NULL;
    00425 }
    00426
    00431 static bool ReadyForNextItem()
    00432 {
    00433   NewsItem *ni = _forced_news == NULL ? _current_news : _forced_news;
    00434   if (ni == NULL) return true;
    00435
    00436   /* Ticker message
    00437    * Check if the status bar message is still being displayed? */
    00438   if (IsNewsTickerShown()) return false;
    00439
    00440   /* Newspaper message, decrement duration counter */
    00441   if (NewsWindow::duration != 0) NewsWindow::duration--;
    00442
    00443   /* neither newsticker nor newspaper are running */
    00444   return (NewsWindow::duration == 0 || FindWindowById(WC_NEWS_WINDOW, 0) == NULL);
    00445 }
    00446
    00448 static void MoveToNextItem()
    00449 {
    00450   InvalidateWindowData(WC_STATUS_BAR, 0, SBI_NEWS_DELETED); // invalidate the statusbar
    00451   DeleteWindowById(WC_NEWS_WINDOW, 0); // close the newspapers window if shown
    00452   _forced_news = NULL;
    00453
    00454   /* if we're not at the last item, then move on */
    00455   if (_current_news != _latest_news) {
    00456     _current_news = (_current_news == NULL) ? _oldest_news : _current_news->next;
    00457     NewsItem *ni = _current_news;
    00458     const NewsType type = _news_subtype_data[ni->subtype].type;
    00459
    00460     /* check the date, don't show too old items */
    00461     if (_date - _news_type_data[type].age > ni->date) return;
    00462
    00463     switch (_news_type_data[type].display) {
    00464       default: NOT_REACHED();
    00465       case ND_OFF: // Off - show nothing only a small reminder in the status bar
    00466         InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_REMINDER);
    00467         break;
    00468
    00469       case ND_SUMMARY: // Summary - show ticker
    00470         ShowTicker(ni);
    00471         break;
    00472
    00473       case ND_FULL: // Full - show newspaper
    00474         ShowNewspaper(ni);
    00475         break;
    00476     }
    00477   }
    00478 }
    00479
    00489 void AddNewsItem(StringID string, NewsSubtype subtype, uint data_a, uint data_b, void *free_data)
    00490 {
    00491   if (_game_mode == GM_MENU) return;
    00492
    00493   /* Create new news item node */
    00494   NewsItem *ni = new NewsItem;
    00495
    00496   ni->string_id = string;
    00497   ni->subtype = subtype;
    00498   ni->flags = _news_subtype_data[subtype].flags;
    00499
    00500   /* show this news message in colour? */
    00501   if (_cur_year >= _settings_client.gui.coloured_news_year) ni->flags |= NF_INCOLOUR;
    00502
    00503   ni->data_a = data_a;
    00504   ni->data_b = data_b;
    00505   ni->free_data = free_data;
    00506   ni->date = _date;
    00507   CopyOutDParam(ni->params, 0, lengthof(ni->params));
    00508
    00509   if (_total_news++ == 0) {
    00510     assert(_oldest_news == NULL);
    00511     _oldest_news = ni;
    00512     ni->prev = NULL;
    00513   } else {
    00514     assert(_latest_news->next == NULL);
    00515     _latest_news->next = ni;
    00516     ni->prev = _latest_news;
    00517   }
    00518
    00519   ni->next = NULL;
    00520   _latest_news = ni;
    00521
    00522   InvalidateWindow(WC_MESSAGE_HISTORY, 0);
    00523 }
    00524
    00526 static void DeleteNewsItem(NewsItem *ni)
    00527 {
    00528   if (_forced_news == ni || _current_news == ni) {
    00529     /* about to remove the currently forced item (shown as newspapers) ||
    00530      * about to remove the currently displayed item (newspapers, ticker, or just a reminder) */
    00531     MoveToNextItem();
    00532   }
    00533
    00534   /* delete item */
    00535
    00536   if (ni->prev != NULL) {
    00537     ni->prev->next = ni->next;
    00538   } else {
    00539     assert(_oldest_news == ni);
    00540     _oldest_news = ni->next;
    00541   }
    00542
    00543   if (ni->next != NULL) {
    00544     ni->next->prev = ni->prev;
    00545   } else {
    00546     assert(_latest_news == ni);
    00547     _latest_news = ni->prev;
    00548   }
    00549
    00550   free(ni->free_data);
    00551
    00552   if (_current_news == ni) _current_news = ni->prev;
    00553   _total_news--;
    00554   delete ni;
    00555
    00556   InvalidateWindow(WC_MESSAGE_HISTORY, 0);
    00557 }
    00558
    00559 void DeleteVehicleNews(VehicleID vid, StringID news)
    00560 {
    00561   NewsItem *ni = _oldest_news;
    00562
    00563   while (ni != NULL) {
    00564     if (ni->flags & NF_VEHICLE &&
    00565         ni->data_a == vid &&
    00566         (news == INVALID_STRING_ID || ni->string_id == news)) {
    00567       /* grab a pointer to the next item before ni is freed */
    00568       NewsItem *p = ni->next;
    00569       DeleteNewsItem(ni);
    00570       ni = p;
    00571     } else {
    00572       ni = ni->next;
    00573     }
    00574   }
    00575 }
    00576
    00581 void DeleteStationNews(StationID sid)
    00582 {
    00583   NewsItem *ni = _oldest_news;
    00584
    00585   while (ni != NULL) {
    00586     NewsItem *next = ni->next;
    00587     switch (ni->subtype) {
    00588       case NS_ARRIVAL_COMPANY:
    00589       case NS_ARRIVAL_OTHER:
    00590       case NS_ACCEPTANCE:
    00591         if (ni->data_b == sid) DeleteNewsItem(ni);
    00592         break;
    00593       default:
    00594         break;
    00595     }
    00596     ni = next;
    00597   }
    00598 }
    00599
    00600 void RemoveOldNewsItems()
    00601 {
    00602   NewsItem *next;
    00603   for (NewsItem *cur = _oldest_news; _total_news > MIN_NEWS_AMOUNT && cur != NULL; cur = next) {
    00604     next = cur->next;
    00605     if (_date - _news_type_data[_news_subtype_data[cur->subtype].type].age * _settings_client.gui.news_message_timeout > cur->date) DeleteNewsItem(cur);
    00606   }
    00607 }
    00608
    00609 void NewsLoop()
    00610 {
    00611   /* no news item yet */
    00612   if (_total_news == 0) return;
    00613
    00614   static byte _last_clean_month = 0;
    00615
    00616   if (_last_clean_month != _cur_month) {
    00617     RemoveOldNewsItems();
    00618     _last_clean_month = _cur_month;
    00619   }
    00620
    00621   if (ReadyForNextItem()) MoveToNextItem();
    00622 }
    00623
    00625 static void ShowNewsMessage(NewsItem *ni)
    00626 {
    00627   assert(_total_news != 0);
    00628
    00629   /* Delete the news window */
    00630   DeleteWindowById(WC_NEWS_WINDOW, 0);
    00631
    00632   /* setup forced news item */
    00633   _forced_news = ni;
    00634
    00635   if (_forced_news != NULL) {
    00636     DeleteWindowById(WC_NEWS_WINDOW, 0);
    00637     ShowNewspaper(ni);
    00638   }
    00639 }
    00640
    00642 void ShowLastNewsMessage()
    00643 {
    00644   if (_total_news == 0) {
    00645     return;
    00646   } else if (_forced_news == NULL) {
    00647     /* Not forced any news yet, show the current one, unless a news window is
    00648      * open (which can only be the current one), then show the previous item */
    00649     const Window *w = FindWindowById(WC_NEWS_WINDOW, 0);
    00650     ShowNewsMessage((w == NULL || (_current_news == _oldest_news)) ? _current_news : _current_news->prev);
    00651   } else if (_forced_news == _oldest_news) {
    00652     /* We have reached the oldest news, start anew with the latest */
    00653     ShowNewsMessage(_latest_news);
    00654   } else {
    00655     /* 'Scrolling' through news history show each one in turn */
    00656     ShowNewsMessage(_forced_news->prev);
    00657   }
    00658 }
    00659
    00660
    00669 static void DrawNewsString(int x, int y, TextColour colour, const NewsItem *ni, uint maxw)
    00670 {
    00671   char buffer[512], buffer2[512];
    00672   StringID str;
    00673
    00674   CopyInDParam(0, ni->params, lengthof(ni->params));
    00675   str = ni->string_id;
    00676
    00677   GetString(buffer, str, lastof(buffer));
    00678   /* Copy the just gotten string to another buffer to remove any formatting
    00679    * from it such as big fonts, etc. */
    00680   const char *ptr = buffer;
    00681   char *dest = buffer2;
    00682   WChar c_last = '\0';
    00683   for (;;) {
    00684     WChar c = Utf8Consume(&ptr);
    00685     if (c == 0) break;
    00686     /* Make a space from a newline, but ignore multiple newlines */
    00687     if (c == '\n' && c_last != '\n') {
    00688       dest[0] = ' ';
    00689       dest++;
    00690     } else if (c == '\r') {
    00691       dest[0] = dest[1] = dest[2] = dest[3] = ' ';
    00692       dest += 4;
    00693     } else if (IsPrintable(c)) {
    00694       dest += Utf8Encode(dest, c);
    00695     }
    00696     c_last = c;
    00697   }
    00698
    00699   *dest = '\0';
    00700   /* Truncate and show string; postfixed by '...' if neccessary */
    00701   DoDrawStringTruncated(buffer2, x, y, colour, maxw);
    00702 }
    00703
    00704
    00705 struct MessageHistoryWindow : Window {
    00706   MessageHistoryWindow(const WindowDesc *desc) : Window(desc)
    00707   {
    00708     this->vscroll.cap = 10;
    00709     this->vscroll.count = _total_news;
    00710     this->resize.step_height = 12;
    00711     this->resize.height = this->height - 12 * 6; // minimum of 4 items in the list, each item 12 high
    00712     this->resize.step_width = 1;
    00713     this->resize.width = 200; // can't make window any smaller than 200 pixel
    00714
    00715     this->FindWindowPlacementAndResize(desc);
    00716   }
    00717
    00718   virtual void OnPaint()
    00719   {
    00720     int y = 19;
    00721
    00722     SetVScrollCount(this, _total_news);
    00723     this->DrawWidgets();
    00724
    00725     if (_total_news == 0) return;
    00726
    00727     NewsItem *ni = _latest_news;
    00728     for (int n = this->vscroll.pos; n > 0; n--) {
    00729       ni = ni->prev;
    00730       if (ni == NULL) return;
    00731     }
    00732
    00733     for (int n = this->vscroll.cap; n > 0; n--) {
    00734       SetDParam(0, ni->date);
    00735       DrawString(4, y, STR_SHORT_DATE, TC_WHITE);
    00736
    00737       DrawNewsString(82, y, TC_WHITE, ni, this->width - 95);
    00738       y += 12;
    00739
    00740       ni = ni->prev;
    00741       if (ni == NULL) return;
    00742     }
    00743   }
    00744
    00745   virtual void OnClick(Point pt, int widget)
    00746   {
    00747     if (widget == 3) {
    00748       NewsItem *ni = _latest_news;
    00749       if (ni == NULL) return;
    00750
    00751       for (int n = (pt.y - 19) / 12 + this->vscroll.pos; n > 0; n--) {
    00752         ni = ni->prev;
    00753         if (ni == NULL) return;
    00754       }
    00755
    00756       ShowNewsMessage(ni);
    00757     }
    00758   }
    00759
    00760   virtual void OnResize(Point new_size, Point delta)
    00761   {
    00762     this->vscroll.cap += delta.y / 12;
    00763   }
    00764 };
    00765
    00766 static const Widget _message_history_widgets[] = {
    00767 {   WWT_CLOSEBOX,   RESIZE_NONE,  COLOUR_BROWN,     0,    10,     0,    13, STR_00C5,            STR_018B_CLOSE_WINDOW},
    00768 {    WWT_CAPTION,  RESIZE_RIGHT,  COLOUR_BROWN,    11,   387,     0,    13, STR_MESSAGE_HISTORY, STR_018C_WINDOW_TITLE_DRAG_THIS},
    00769 {  WWT_STICKYBOX,     RESIZE_LR,  COLOUR_BROWN,   388,   399,     0,    13, 0x0,                 STR_STICKY_BUTTON},
    00770 {      WWT_PANEL,     RESIZE_RB,  COLOUR_BROWN,     0,   387,    14,   139, 0x0,                 STR_MESSAGE_HISTORY_TIP},
    00771 {  WWT_SCROLLBAR,    RESIZE_LRB,  COLOUR_BROWN,   388,   399,    14,   127, 0x0,                 STR_0190_SCROLL_BAR_SCROLLS_LIST},
    00772 {  WWT_RESIZEBOX,   RESIZE_LRTB,  COLOUR_BROWN,   388,   399,   128,   139, 0x0,                 STR_RESIZE_BUTTON},
    00773 {   WIDGETS_END},
    00774 };
    00775
    00776 static const WindowDesc _message_history_desc(
    00777   240, 22, 400, 140, 400, 140,
    00778   WC_MESSAGE_HISTORY, WC_NONE,
    00779   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
    00780   _message_history_widgets
    00781 );
    00782
    00784 void ShowMessageHistory()
    00785 {
    00786   DeleteWindowById(WC_MESSAGE_HISTORY, 0);
    00787   new MessageHistoryWindow(&_message_history_desc);
    00788 }
    00789
    00790
    00792 enum {
    00793   WIDGET_NEWSOPT_DROP_SUMMARY = 4,
    00794   WIDGET_NEWSOPT_SOUNDTICKER  = 6,
    00795   WIDGET_NEWSOPT_START_OPTION = 8,
    00796 };
    00797
    00798 static const StringID _message_opt[] = {STR_OFF, STR_SUMMARY, STR_FULL, INVALID_STRING_ID};
    00799
    00800 struct MessageOptionsWindow : Window {
    00801   int state;
    00802
    00803   MessageOptionsWindow(const WindowDesc *desc) : Window(desc)
    00804   {
    00805     NewsDisplay all_val;
    00806
    00807     /* Set up the initial disabled buttons in the case of 'off' or 'full' */
    00808     all_val = _news_type_data[0].display;
    00809     for (int i = 0; i < NT_END; i++) {
    00810       this->SetMessageButtonStates(_news_type_data[i].display, i);
    00811       /* If the value doesn't match the ALL-button value, set the ALL-button value to 'off' */
    00812       if (_news_type_data[i].display != all_val) all_val = ND_OFF;
    00813     }
    00814     /* If all values are the same value, the ALL-button will take over this value */
    00815     this->state = all_val;
    00816
    00817     this->FindWindowPlacementAndResize(desc);
    00818   }
    00819
    00828   void SetMessageButtonStates(byte value, int element)
    00829   {
    00830     element *= NB_WIDG_PER_SETTING;
    00831
    00832     this->SetWidgetDisabledState(element + WIDGET_NEWSOPT_START_OPTION, value == 0);
    00833     this->SetWidgetDisabledState(element + WIDGET_NEWSOPT_START_OPTION + 2, value == 2);
    00834   }
    00835
    00836   virtual void OnPaint()
    00837   {
    00838     if (_news_ticker_sound) this->LowerWidget(WIDGET_NEWSOPT_SOUNDTICKER);
    00839
    00840     this->widget[WIDGET_NEWSOPT_DROP_SUMMARY].data = _message_opt[this->state];
    00841     this->DrawWidgets();
    00842
    00843     /* Draw the string of each setting on each button. */
    00844     for (int i = 0, y = 26; i < NT_END; i++, y += 12) {
    00845       /* 51 comes from 13 + 89 (left and right of the button)+1, shiefted by one as to get division,
    00846        * which will give centered position */
    00847       DrawStringCentered(51, y + 1, _message_opt[_news_type_data[i].display], TC_BLACK);
    00848     }
    00849   }
    00850
    00851   virtual void OnClick(Point pt, int widget)
    00852   {
    00853     switch (widget) {
    00854       case WIDGET_NEWSOPT_DROP_SUMMARY: // Dropdown menu for all settings
    00855         ShowDropDownMenu(this, _message_opt, this->state, WIDGET_NEWSOPT_DROP_SUMMARY, 0, 0);
    00856         break;
    00857
    00858       case WIDGET_NEWSOPT_SOUNDTICKER: // Change ticker sound on/off
    00859         _news_ticker_sound ^= 1;
    00860         this->ToggleWidgetLoweredState(widget);
    00861         this->InvalidateWidget(widget);
    00862         break;
    00863
    00864       default: { // Clicked on the [<] .. [>] widgets
    00865         int wid = widget - WIDGET_NEWSOPT_START_OPTION;
    00866         if (wid >= 0 && wid < (NB_WIDG_PER_SETTING * NT_END)) {
    00867           int element = wid / NB_WIDG_PER_SETTING;
    00868           byte val = (_news_type_data[element].display + ((wid % NB_WIDG_PER_SETTING) ? 1 : -1)) % 3;
    00869
    00870           this->SetMessageButtonStates(val, element);
    00871           _news_type_data[element].display = (NewsDisplay)val;
    00872           this->SetDirty();
    00873         }
    00874         break;
    00875       }
    00876     }
    00877   }
    00878
    00879   virtual void OnDropdownSelect(int widget, int index)
    00880   {
    00881     this->state = index;
    00882
    00883     for (int i = 0; i < NT_END; i++) {
    00884       this->SetMessageButtonStates(index, i);
    00885       _news_type_data[i].display = (NewsDisplay)index;
    00886     }
    00887     this->SetDirty();
    00888   }
    00889 };
    00890
    00891
    00892 /*
    00893  * The news settings window widgets
    00894  *
    00895  * Main part of the window is a list of news setting lines, one for each news category.
    00896  * Each line is constructed by an expansion of the \c NEWS_SETTINGS_LINE macro
    00897  */
    00898
    00915 #define NEWS_SETTINGS_LINE(basey, linenum, text) \
    00916   { WWT_PUSHIMGBTN, RESIZE_NONE, COLOUR_YELLOW, \
    00917       4,  12,  basey     + linenum * NEWS_SETTING_BASELINE_SKIP,  basey + 11 + linenum * NEWS_SETTING_BASELINE_SKIP, \
    00918     SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, \
    00919   { WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_YELLOW, \
    00920      13,  89,  basey     + linenum * NEWS_SETTING_BASELINE_SKIP,  basey + 11 + linenum * NEWS_SETTING_BASELINE_SKIP, \
    00921     STR_EMPTY, STR_NULL}, \
    00922   { WWT_PUSHIMGBTN, RESIZE_NONE, COLOUR_YELLOW, \
    00923      90,  98,  basey     + linenum * NEWS_SETTING_BASELINE_SKIP,  basey + 11 + linenum * NEWS_SETTING_BASELINE_SKIP, \
    00924     SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, \
    00925         { WWT_TEXT, RESIZE_NONE, COLOUR_YELLOW, \
    00926     103, 409,  basey + 1 + linenum * NEWS_SETTING_BASELINE_SKIP,  basey + 13 + linenum * NEWS_SETTING_BASELINE_SKIP, \
    00927     text, STR_NULL}
    00928
    00929 static const int NEWS_SETTING_BASELINE_SKIP = 12;
    00930
    00931
    00932 static const Widget _message_options_widgets[] = {
    00933 { WWT_CLOSEBOX, RESIZE_NONE, COLOUR_BROWN,   0,  10,  0, 13,
    00934   STR_00C5,                 STR_018B_CLOSE_WINDOW},
    00935 {  WWT_CAPTION, RESIZE_NONE, COLOUR_BROWN,  11, 409,  0, 13,
    00936   STR_0204_MESSAGE_OPTIONS, STR_018C_WINDOW_TITLE_DRAG_THIS},
    00937 {    WWT_PANEL, RESIZE_NONE, COLOUR_BROWN,   0, 409, 14, 64 + NT_END * NEWS_SETTING_BASELINE_SKIP,
    00938   0x0,                      STR_NULL},
    00939
    00940 /* Text at the top of the main panel, in black */
    00941 {    WWT_LABEL, RESIZE_NONE, COLOUR_BROWN,
    00942     0, 409, 13, 26,
    00943   STR_0205_MESSAGE_TYPES,   STR_NULL},
    00944
    00945 /* General drop down and sound button, widgets WIDGET_NEWSOPT_BTN_SUMMARY and WIDGET_NEWSOPT_DROP_SUMMARY */
    00946 {  WWT_DROPDOWN, RESIZE_NONE, COLOUR_YELLOW,
    00947     4,  98,  34 + NT_END * NEWS_SETTING_BASELINE_SKIP,  45 + NT_END * NEWS_SETTING_BASELINE_SKIP,
    00948   0x0, STR_NULL},
    00949
    00950 {      WWT_TEXT, RESIZE_NONE, COLOUR_YELLOW,
    00951   103, 409,  35 + NT_END * NEWS_SETTING_BASELINE_SKIP,  47 + NT_END * NEWS_SETTING_BASELINE_SKIP,
    00952   STR_MESSAGES_ALL, STR_NULL},
    00953
    00954 /* Below is widget WIDGET_NEWSOPT_SOUNDTICKER */
    00955 { WWT_TEXTBTN_2, RESIZE_NONE, COLOUR_YELLOW,
    00956     4,  98,  46 + NT_END * NEWS_SETTING_BASELINE_SKIP,  57 + NT_END * NEWS_SETTING_BASELINE_SKIP,
    00957   STR_02DB_OFF,  STR_NULL},
    00958
    00959 {      WWT_TEXT, RESIZE_NONE, COLOUR_YELLOW,
    00960   103, 409,  47 + NT_END * NEWS_SETTING_BASELINE_SKIP,  59 + NT_END * NEWS_SETTING_BASELINE_SKIP,
    00961   STR_MESSAGE_SOUND, STR_NULL},
    00962
    00963 /* List of news-setting lines (4 widgets for each line).
    00964  * First widget must be number WIDGET_NEWSOPT_START_OPTION
    00965  */
    00966 NEWS_SETTINGS_LINE(26, NT_ARRIVAL_COMPANY,  STR_0206_ARRIVAL_OF_FIRST_VEHICLE),
    00967 NEWS_SETTINGS_LINE(26, NT_ARRIVAL_OTHER,    STR_0207_ARRIVAL_OF_FIRST_VEHICLE),
    00968 NEWS_SETTINGS_LINE(26, NT_ACCIDENT,         STR_0208_ACCIDENTS_DISASTERS),
    00969 NEWS_SETTINGS_LINE(26, NT_COMPANY_INFO,     STR_0209_COMPANY_INFORMATION),
    00970 NEWS_SETTINGS_LINE(26, NT_INDUSTRY_OPEN,    STR_NEWS_INDUSTRY_OPEN),
    00971 NEWS_SETTINGS_LINE(26, NT_INDUSTRY_CLOSE,   STR_NEWS_INDUSTRY_CLOSE),
    00972 NEWS_SETTINGS_LINE(26, NT_ECONOMY,          STR_020A_ECONOMY_CHANGES),
    00973 NEWS_SETTINGS_LINE(26, NT_INDUSTRY_COMPANY, STR_INDUSTRY_CHANGES_SERVED_BY_COMPANY),
    00974 NEWS_SETTINGS_LINE(26, NT_INDUSTRY_OTHER,   STR_INDUSTRY_CHANGES_SERVED_BY_OTHER),
    00975 NEWS_SETTINGS_LINE(26, NT_INDUSTRY_NOBODY,  STR_OTHER_INDUSTRY_PRODUCTION_CHANGES),
    00976 NEWS_SETTINGS_LINE(26, NT_ADVICE,           STR_020B_ADVICE_INFORMATION_ON_COMPANY),
    00977 NEWS_SETTINGS_LINE(26, NT_NEW_VEHICLES,     STR_020C_NEW_VEHICLES),
    00978 NEWS_SETTINGS_LINE(26, NT_ACCEPTANCE,       STR_020D_CHANGES_OF_CARGO_ACCEPTANCE),
    00979 NEWS_SETTINGS_LINE(26, NT_SUBSIDIES,        STR_020E_SUBSIDIES),
    00980 NEWS_SETTINGS_LINE(26, NT_GENERAL,          STR_020F_GENERAL_INFORMATION),
    00981
    00982 {   WIDGETS_END},
    00983 };
    00984
    00985 static const WindowDesc _message_options_desc(
    00986   270,  22,  410,  65 + NT_END * NEWS_SETTING_BASELINE_SKIP,
    00987              410,  65 + NT_END * NEWS_SETTING_BASELINE_SKIP,
    00988   WC_GAME_OPTIONS, WC_NONE,
    00989   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
    00990   _message_options_widgets
    00991 );
    00992
    00993 void ShowMessageOptions()
    00994 {
    00995   DeleteWindowById(WC_GAME_OPTIONS, 0);
    00996   new MessageOptionsWindow(&_message_options_desc);
    00997 }