# news_gui.cpp

Go to the documentation of this file.

    00001 /* $Id: news_gui.cpp 12868 2008-04-24 12:32:37Z rubidium $ */
    00002
    00003
    00004 #include "stdafx.h"
    00005 #include "openttd.h"
    00006 #include "gui.h"
    00007 #include "window_gui.h"
    00008 #include "viewport_func.h"
    00009 #include "news.h"
    00010 #include "settings_type.h"
    00011 #include "transparency.h"
    00012 #include "strings_func.h"
    00013 #include "window_func.h"
    00014 #include "date_func.h"
    00015 #include "vehicle_base.h"
    00016 #include "sound_func.h"
    00017 #include "string_func.h"
    00018 #include "widgets/dropdown_func.h"
    00019
    00020 #include "table/sprites.h"
    00021 #include "table/strings.h"
    00022
    00049 #define MAX_NEWS 30
    00050 #define NB_WIDG_PER_SETTING 4
    00051
    00052 typedef byte NewsID;
    00053 #define INVALID_NEWS 255
    00054
    00055 NewsItem _statusbar_news_item;
    00056 uint32 _news_display_opt;
    00057 bool _news_ticker_sound;
    00058 static NewsItem _news_items[MAX_NEWS];
    00059 static NewsID _current_news = INVALID_NEWS;
    00060 static NewsID _oldest_news = 0;
    00061 static NewsID _latest_news = INVALID_NEWS;
    00062
    00067 static NewsID _forced_news = INVALID_NEWS;
    00068
    00069 static byte _total_news = 0;
    00070
    00071 void DrawNewsNewVehicleAvail(Window *w);
    00072 void DrawNewsBankrupcy(Window *w);
    00073 static void MoveToNextItem();
    00074
    00075 StringID GetNewsStringNewVehicleAvail(const NewsItem *ni);
    00076 StringID GetNewsStringBankrupcy(const NewsItem *ni);
    00077
    00078 static DrawNewsCallbackProc * const _draw_news_callback[] = {
    00079   DrawNewsNewVehicleAvail,
    00080   DrawNewsBankrupcy,
    00081 };
    00082
    00083 extern GetNewsStringCallbackProc * const _get_news_string_callback[];
    00084 GetNewsStringCallbackProc * const _get_news_string_callback[] = {
    00085   GetNewsStringNewVehicleAvail,
    00086   GetNewsStringBankrupcy,
    00087 };
    00088
    00090 void InitNewsItemStructs()
    00091 {
    00092   memset(_news_items, 0, sizeof(_news_items));
    00093   _current_news = INVALID_NEWS;
    00094   _oldest_news = 0;
    00095   _latest_news = INVALID_NEWS;
    00096   _forced_news = INVALID_NEWS;
    00097   _total_news = 0;
    00098 }
    00099
    00100 void DrawNewsBorder(const Window *w)
    00101 {
    00102   int left = 0;
    00103   int right = w->width - 1;
    00104   int top = 0;
    00105   int bottom = w->height - 1;
    00106
    00107   GfxFillRect(left, top, right, bottom, 0xF);
    00108
    00109   GfxFillRect(left, top, left, bottom, 0xD7);
    00110   GfxFillRect(right, top, right, bottom, 0xD7);
    00111   GfxFillRect(left, top, right, top, 0xD7);
    00112   GfxFillRect(left, bottom, right, bottom, 0xD7);
    00113
    00114   DrawString(left + 2, top + 1, STR_00C6, TC_FROMSTRING);
    00115 }
    00116
    00117 static void NewsWindowProc(Window *w, WindowEvent *e)
    00118 {
    00119   switch (e->event) {
    00120   case WE_CREATE: { // If chatbar is open at creation time, we need to go above it
    00121     const Window *w1 = FindWindowById(WC_SEND_NETWORK_MSG, 0);
    00122     w->message.msg = (w1 != NULL) ? w1->height : 0;
    00123   } break;
    00124
    00125   case WE_PAINT: {
    00126     const NewsItem *ni = WP(w, news_d).ni;
    00127     ViewPort *vp;
    00128
    00129     switch (ni->display_mode) {
    00130       case NM_NORMAL:
    00131       case NM_THIN: {
    00132         DrawNewsBorder(w);
    00133
    00134         DrawString(2, 1, STR_00C6, TC_FROMSTRING);
    00135
    00136         SetDParam(0, ni->date);
    00137         DrawStringRightAligned(428, 1, STR_01FF, TC_FROMSTRING);
    00138
    00139         if (!(ni->flags & NF_VIEWPORT)) {
    00140           CopyInDParam(0, ni->params, lengthof(ni->params));
    00141           DrawStringMultiCenter(215, ni->display_mode == NM_NORMAL ? 76 : 56,
    00142             ni->string_id, w->width - 4);
    00143         } else {
    00144           /* Back up transparency options to draw news view */
    00145           TransparencyOptionBits to_backup = _transparency_opt;
    00146           _transparency_opt = 0;
    00147           DrawWindowViewport(w);
    00148           _transparency_opt = to_backup;
    00149
    00150           /* Shade the viewport into gray, or color*/
    00151           vp = w->viewport;
    00152           GfxFillRect(vp->left - w->left, vp->top - w->top,
    00153             vp->left - w->left + vp->width - 1, vp->top - w->top + vp->height - 1,
    00154             (ni->flags & NF_INCOLOR ? PALETTE_TO_TRANSPARENT : PALETTE_TO_STRUCT_GREY) | (1 << USE_COLORTABLE)
    00155           );
    00156
    00157           CopyInDParam(0, ni->params, lengthof(ni->params));
    00158           DrawStringMultiCenter(w->width / 2, 20, ni->string_id, w->width - 4);
    00159         }
    00160         break;
    00161       }
    00162
    00163       case NM_CALLBACK: {
    00164         _draw_news_callback[ni->callback](w);
    00165         break;
    00166       }
    00167
    00168       default: {
    00169         DrawWindowWidgets(w);
    00170         if (!(ni->flags & NF_VIEWPORT)) {
    00171           CopyInDParam(0, ni->params, lengthof(ni->params));
    00172           DrawStringMultiCenter(140, 38, ni->string_id, 276);
    00173         } else {
    00174           DrawWindowViewport(w);
    00175           CopyInDParam(0, ni->params, lengthof(ni->params));
    00176           DrawStringMultiCenter(w->width / 2, w->height - 16, ni->string_id, w->width - 4);
    00177         }
    00178         break;
    00179       }
    00180     }
    00181   } break;
    00182
    00183   case WE_CLICK: {
    00184     switch (e->we.click.widget) {
    00185     case 1: {
    00186       NewsItem *ni = WP(w, news_d).ni;
    00187       DeleteWindow(w);
    00188       ni->duration = 0;
    00189       _forced_news = INVALID_NEWS;
    00190     } break;
    00191     case 0: {
    00192       NewsItem *ni = WP(w, news_d).ni;
    00193       if (ni->flags & NF_VEHICLE) {
    00194         Vehicle *v = GetVehicle(ni->data_a);
    00195         ScrollMainWindowTo(v->x_pos, v->y_pos);
    00196       } else if (ni->flags & NF_TILE) {
    00197         if (!ScrollMainWindowToTile(ni->data_a) && ni->data_b != 0)
    00198           ScrollMainWindowToTile(ni->data_b);
    00199       }
    00200     } break;
    00201     }
    00202   } break;
    00203
    00204   case WE_KEYPRESS:
    00205     if (e->we.keypress.keycode == WKC_SPACE) {
    00206       /* Don't continue. */
    00207       e->we.keypress.cont = false;
    00208       DeleteWindow(w);
    00209     }
    00210     break;
    00211
    00212   case WE_MESSAGE: // The chatbar has notified us that is was either created or closed
    00213     switch (e->we.message.msg) {
    00214       case WE_CREATE: w->message.msg = e->we.message.wparam; break;
    00215       case WE_DESTROY: w->message.msg = 0; break;
    00216     }
    00217     break;
    00218
    00219   case WE_TICK: { // Scroll up newsmessages from the bottom in steps of 4 pixels
    00220     int diff;
    00221     int y = max(w->top - 4, _screen.height - w->height - 12 - w->message.msg);
    00222     if (y == w->top) return;
    00223
    00224     if (w->viewport != NULL)
    00225       w->viewport->top += y - w->top;
    00226
    00227     diff = Delta(w->top, y);
    00228     w->top = y;
    00229
    00230     SetDirtyBlocks(w->left, w->top - diff, w->left + w->width, w->top + w->height);
    00231   } break;
    00232   }
    00233 }
    00234
    00239 static inline NewsID increaseIndex(NewsID i)
    00240 {
    00241   assert(i != INVALID_NEWS);
    00242   return (i + 1) % MAX_NEWS;
    00243 }
    00244
    00249 static inline NewsID decreaseIndex(NewsID i)
    00250 {
    00251   assert(i != INVALID_NEWS);
    00252   return (i + MAX_NEWS - 1) % MAX_NEWS;
    00253 }
    00254
    00281 void AddNewsItem(StringID string, uint32 flags, uint data_a, uint data_b)
    00282 {
    00283   NewsID l_news;
    00284
    00285   if (_game_mode == GM_MENU) return;
    00286
    00287   /* check the rare case that the oldest (to be overwritten) news item is open */
    00288   if (_total_news == MAX_NEWS && (_oldest_news == _current_news || _oldest_news == _forced_news))
    00289     MoveToNextItem();
    00290
    00291   if (_total_news < MAX_NEWS) _total_news++;
    00292
    00293   /* Increase _latest_news. If we have no news yet, use _oldest news as an
    00294    * index. We cannot use 0 as _oldest_news can jump around due to
    00295    * DeleteVehicleNews */
    00296   l_news = _latest_news;
    00297   _latest_news = (_latest_news == INVALID_NEWS) ? _oldest_news : increaseIndex(_latest_news);
    00298
    00299   /* If the fifo-buffer is full, overwrite the oldest entry */
    00300   if (l_news != INVALID_NEWS && _latest_news == _oldest_news) {
    00301     assert(_total_news == MAX_NEWS);
    00302     _oldest_news = increaseIndex(_oldest_news);
    00303   }
    00304
    00305   /*DEBUG(misc, 0, "+cur %3d, old %2d, lat %3d, for %3d, tot %2d",
    00306     _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/
    00307
    00308   /* Add news to _latest_news */
    00309   {
    00310     Window *w;
    00311     NewsItem *ni = &_news_items[_latest_news];
    00312     memset(ni, 0, sizeof(*ni));
    00313
    00314     ni->string_id = string;
    00315     ni->display_mode = (byte)flags;
    00316     ni->flags = (byte)(flags >> 8);
    00317
    00318     /* show this news message in color? */
    00319     if (_cur_year >= _patches.colored_news_year) ni->flags |= NF_INCOLOR;
    00320
    00321     ni->type = (byte)(flags >> 16);
    00322     ni->callback = (byte)(flags >> 24);
    00323     ni->data_a = data_a;
    00324     ni->data_b = data_b;
    00325     ni->date = _date;
    00326     CopyOutDParam(ni->params, 0, lengthof(ni->params));
    00327
    00328     w = FindWindowById(WC_MESSAGE_HISTORY, 0);
    00329     if (w == NULL) return;
    00330     SetWindowDirty(w);
    00331     w->vscroll.count = _total_news;
    00332   }
    00333 }
    00334
    00335
    00341 static const byte _news_items_age[NT_END] = {
    00342   60,
    00343   60,
    00344   90,
    00345   60,
    00346   90,
    00347   30,
    00348   30,
    00349   30,
    00350   30,
    00351   150,
    00352   30,
    00353   90,
    00354   180,
    00355   60
    00356 };
    00357
    00358
    00359 static const Widget _news_type13_widgets[] = {
    00360 {      WWT_PANEL,   RESIZE_NONE,    15,     0,   429,     0,   169, 0x0, STR_NULL},
    00361 {      WWT_PANEL,   RESIZE_NONE,    15,     0,    10,     0,    11, 0x0, STR_NULL},
    00362 {   WIDGETS_END},
    00363 };
    00364
    00365 static WindowDesc _news_type13_desc = {
    00366   WDP_CENTER, 476, 430, 170, 430, 170,
    00367   WC_NEWS_WINDOW, WC_NONE,
    00368   WDF_DEF_WIDGET,
    00369   _news_type13_widgets,
    00370   NewsWindowProc
    00371 };
    00372
    00373 static const Widget _news_type2_widgets[] = {
    00374 {      WWT_PANEL,   RESIZE_NONE,    15,     0,   429,     0,   129, 0x0, STR_NULL},
    00375 {      WWT_PANEL,   RESIZE_NONE,    15,     0,    10,     0,    11, 0x0, STR_NULL},
    00376 {   WIDGETS_END},
    00377 };
    00378
    00379 static WindowDesc _news_type2_desc = {
    00380   WDP_CENTER, 476, 430, 130, 430, 130,
    00381   WC_NEWS_WINDOW, WC_NONE,
    00382   WDF_DEF_WIDGET,
    00383   _news_type2_widgets,
    00384   NewsWindowProc
    00385 };
    00386
    00387 static const Widget _news_type0_widgets[] = {
    00388 {      WWT_PANEL,   RESIZE_NONE,     5,     0,   279,    14,    86, 0x0,              STR_NULL},
    00389 {   WWT_CLOSEBOX,   RESIZE_NONE,     5,     0,    10,     0,    13, STR_00C5,         STR_018B_CLOSE_WINDOW},
    00390 {    WWT_CAPTION,   RESIZE_NONE,     5,    11,   279,     0,    13, STR_012C_MESSAGE, STR_NULL},
    00391 {      WWT_INSET,   RESIZE_NONE,     5,     2,   277,    16,    64, 0x0,              STR_NULL},
    00392 {   WIDGETS_END},
    00393 };
    00394
    00395 static WindowDesc _news_type0_desc = {
    00396   WDP_CENTER, 476, 280, 87, 280, 87,
    00397   WC_NEWS_WINDOW, WC_NONE,
    00398   WDF_DEF_WIDGET,
    00399   _news_type0_widgets,
    00400   NewsWindowProc
    00401 };
    00402
    00403 static const SoundFx _news_sounds[NT_END] = {
    00404   SND_1D_APPLAUSE,
    00405   SND_1D_APPLAUSE,
    00406   SND_BEGIN,
    00407   SND_BEGIN,
    00408   SND_BEGIN,
    00409   SND_BEGIN,
    00410   SND_BEGIN,
    00411   SND_BEGIN,
    00412   SND_BEGIN,
    00413   SND_BEGIN,
    00414   SND_1E_OOOOH,
    00415   SND_BEGIN,
    00416   SND_BEGIN,
    00417   SND_BEGIN,
    00418 };
    00419
    00420 const char *_news_display_name[NT_END] = {
    00421   "arrival_player",
    00422   "arrival_other",
    00423   "accident",
    00424   "company_info",
    00425   "openclose",
    00426   "economy",
    00427   "production_player",
    00428   "production_other",
    00429   "production_nobody",
    00430   "advice",
    00431   "new_vehicles",
    00432   "acceptance",
    00433   "subsidies",
    00434   "general",
    00435 };
    00436
    00443 static inline byte GetNewsDisplayValue(byte item)
    00444 {
    00445   assert(item < NT_END && GB(_news_display_opt, item * 2, 2) <= 2);
    00446   return GB(_news_display_opt, item * 2, 2);
    00447 }
    00448
    00455 static inline void SetNewsDisplayValue(byte item, byte val)
    00456 {
    00457   assert(item < NT_END && val <= 2);
    00458   SB(_news_display_opt, item * 2, 2, val);
    00459 }
    00460
    00462 static void ShowNewspaper(NewsItem *ni)
    00463 {
    00464   Window *w;
    00465   SoundFx sound;
    00466   int top;
    00467   ni->flags &= ~NF_FORCE_BIG;
    00468   ni->duration = 555;
    00469
    00470   sound = _news_sounds[ni->type];
    00471   if (sound != 0) SndPlayFx(sound);
    00472
    00473   top = _screen.height;
    00474   switch (ni->display_mode) {
    00475     case NM_NORMAL:
    00476     case NM_CALLBACK: {
    00477       _news_type13_desc.top = top;
    00478       w = AllocateWindowDesc(&_news_type13_desc);
    00479       if (ni->flags & NF_VIEWPORT)
    00480         AssignWindowViewport(w, 2, 58, 0x1AA, 0x6E,
    00481           ni->data_a | (ni->flags & NF_VEHICLE ? 0x80000000 : 0), ZOOM_LVL_NEWS);
    00482       break;
    00483     }
    00484
    00485     case NM_THIN: {
    00486       _news_type2_desc.top = top;
    00487       w = AllocateWindowDesc(&_news_type2_desc);
    00488       if (ni->flags & NF_VIEWPORT)
    00489         AssignWindowViewport(w, 2, 58, 0x1AA, 0x46,
    00490           ni->data_a | (ni->flags & NF_VEHICLE ? 0x80000000 : 0), ZOOM_LVL_NEWS);
    00491       break;
    00492     }
    00493
    00494     default: {
    00495       _news_type0_desc.top = top;
    00496       w = AllocateWindowDesc(&_news_type0_desc);
    00497       if (ni->flags & NF_VIEWPORT)
    00498         AssignWindowViewport(w, 3, 17, 0x112, 0x2F,
    00499           ni->data_a | (ni->flags & NF_VEHICLE ? 0x80000000 : 0), ZOOM_LVL_NEWS);
    00500       break;
    00501     }
    00502   }
    00503
    00504   /*DEBUG(misc, 0, " cur %3d, old %2d, lat %3d, for %3d, tot %2d",
    00505     _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/
    00506
    00507   WP(w, news_d).ni = &_news_items[_forced_news == INVALID_NEWS ? _current_news : _forced_news];
    00508   w->flags4 |= WF_DISABLE_VP_SCROLL;
    00509 }
    00510
    00512 static void ShowTicker(const NewsItem *ni)
    00513 {
    00514   Window *w;
    00515
    00516   if (_news_ticker_sound) SndPlayFx(SND_16_MORSE);
    00517
    00518   _statusbar_news_item = *ni;
    00519   w = FindWindowById(WC_STATUS_BAR, 0);
    00520   if (w != NULL) WP(w, def_d).data_1 = 360;
    00521 }
    00522
    00523
    00528 static bool ReadyForNextItem()
    00529 {
    00530   const Window *w;
    00531   NewsID item = (_forced_news == INVALID_NEWS) ? _current_news : _forced_news;
    00532   NewsItem *ni;
    00533
    00534   if (item >= MAX_NEWS) return true;
    00535   ni = &_news_items[item];
    00536
    00537   /* Ticker message
    00538    * Check if the status bar message is still being displayed? */
    00539   w = FindWindowById(WC_STATUS_BAR, 0);
    00540   if (w != NULL && WP(w, const def_d).data_1 > -1280) return false;
    00541
    00542   /* Newspaper message, decrement duration counter */
    00543   if (ni->duration != 0) ni->duration--;
    00544
    00545   /* neither newsticker nor newspaper are running */
    00546   return (ni->duration == 0 || FindWindowById(WC_NEWS_WINDOW, 0) == NULL);
    00547 }
    00548
    00550 static void MoveToNextItem()
    00551 {
    00552   DeleteWindowById(WC_NEWS_WINDOW, 0);
    00553   _forced_news = INVALID_NEWS;
    00554
    00555   /* if we're not at the last item, then move on */
    00556   if (_current_news != _latest_news) {
    00557     NewsItem *ni;
    00558
    00559     _current_news = (_current_news == INVALID_NEWS) ? _oldest_news : increaseIndex(_current_news);
    00560     ni = &_news_items[_current_news];
    00561
    00562     /* check the date, don't show too old items */
    00563     if (_date - _news_items_age[ni->type] > ni->date) return;
    00564
    00565     switch (GetNewsDisplayValue(ni->type)) {
    00566       default: NOT_REACHED();
    00567       case 0: { // Off - show nothing only a small reminder in the status bar
    00568         Window *w = FindWindowById(WC_STATUS_BAR, 0);
    00569
    00570         if (w != NULL) {
    00571           WP(w, def_d).data_2 = 91;
    00572           SetWindowDirty(w);
    00573         }
    00574         break;
    00575       }
    00576
    00577       case 1: // Summary - show ticker, but if forced big, cascade to full
    00578         if (!(ni->flags & NF_FORCE_BIG)) {
    00579           ShowTicker(ni);
    00580           break;
    00581         }
    00582         /* Fallthrough */
    00583
    00584       case 2: // Full - show newspaper
    00585         ShowNewspaper(ni);
    00586         break;
    00587     }
    00588   }
    00589 }
    00590
    00591 void NewsLoop()
    00592 {
    00593   /* no news item yet */
    00594   if (_total_news == 0) return;
    00595
    00596   if (ReadyForNextItem()) MoveToNextItem();
    00597 }
    00598
    00600 static void ShowNewsMessage(NewsID i)
    00601 {
    00602   if (_total_news == 0) return;
    00603
    00604   /* Delete the news window */
    00605   DeleteWindowById(WC_NEWS_WINDOW, 0);
    00606
    00607   /* setup forced news item */
    00608   _forced_news = i;
    00609
    00610   if (_forced_news != INVALID_NEWS) {
    00611     NewsItem *ni = &_news_items[_forced_news];
    00612     ni->duration = 555;
    00613     ni->flags |= NF_FORCE_BIG;
    00614     DeleteWindowById(WC_NEWS_WINDOW, 0);
    00615     ShowNewspaper(ni);
    00616   }
    00617 }
    00618
    00620 void ShowLastNewsMessage()
    00621 {
    00622   if (_forced_news == INVALID_NEWS) {
    00623     /* Not forced any news yet, show the current one, unless a news window is
    00624      * open (which can only be the current one), then show the previous item */
    00625     const Window *w = FindWindowById(WC_NEWS_WINDOW, 0);
    00626     ShowNewsMessage((w == NULL || (_current_news == _oldest_news)) ? _current_news : decreaseIndex(_current_news));
    00627   } else if (_forced_news == _oldest_news) {
    00628     /* We have reached the oldest news, start anew with the latest */
    00629     ShowNewsMessage(_latest_news);
    00630   } else {
    00631     /* 'Scrolling' through news history show each one in turn */
    00632     ShowNewsMessage(decreaseIndex(_forced_news));
    00633   }
    00634 }
    00635
    00636
    00637 /* return news by number, with 0 being the most
    00638  * recent news. Returns INVALID_NEWS if end of queue reached. */
    00639 static NewsID getNews(NewsID i)
    00640 {
    00641   if (i >= _total_news) return INVALID_NEWS;
    00642
    00643   if (_latest_news < i) {
    00644     i = _latest_news + MAX_NEWS - i;
    00645   } else {
    00646     i = _latest_news - i;
    00647   }
    00648
    00649   i %= MAX_NEWS;
    00650   return i;
    00651 }
    00652
    00661 static void DrawNewsString(int x, int y, uint16 color, const NewsItem *ni, uint maxw)
    00662 {
    00663   char buffer[512], buffer2[512];
    00664   const char *ptr;
    00665   char *dest;
    00666   StringID str;
    00667
    00668   if (ni->display_mode == NM_CALLBACK) {
    00669     str = _get_news_string_callback[ni->callback](ni);
    00670   } else {
    00671     CopyInDParam(0, ni->params, lengthof(ni->params));
    00672     str = ni->string_id;
    00673   }
    00674
    00675   GetString(buffer, str, lastof(buffer));
    00676   /* Copy the just gotten string to another buffer to remove any formatting
    00677    * from it such as big fonts, etc. */
    00678   ptr  = buffer;
    00679   dest = buffer2;
    00680   WChar c_last = '\0';
    00681   for (;;) {
    00682     WChar c = Utf8Consume(&ptr);
    00683     if (c == 0) break;
    00684     /* Make a space from a newline, but ignore multiple newlines */
    00685     if (c == '\n' && c_last != '\n') {
    00686       dest[0] = ' ';
    00687       dest++;
    00688     } else if (c == '\r') {
    00689       dest[0] = dest[1] = dest[2] = dest[3] = ' ';
    00690       dest += 4;
    00691     } else if (IsPrintable(c)) {
    00692       dest += Utf8Encode(dest, c);
    00693     }
    00694     c_last = c;
    00695   }
    00696
    00697   *dest = '\0';
    00698   /* Truncate and show string; postfixed by '...' if neccessary */
    00699   DoDrawStringTruncated(buffer2, x, y, color, maxw);
    00700 }
    00701
    00702
    00703 static void MessageHistoryWndProc(Window *w, WindowEvent *e)
    00704 {
    00705   switch (e->event) {
    00706   case WE_PAINT: {
    00707     int y = 19;
    00708     NewsID p, show;
    00709
    00710     SetVScrollCount(w, _total_news);
    00711     DrawWindowWidgets(w);
    00712
    00713     if (_total_news == 0) break;
    00714     show = min(_total_news, w->vscroll.cap);
    00715
    00716     for (p = w->vscroll.pos; p < w->vscroll.pos + show; p++) {
    00717       /* get news in correct order */
    00718       const NewsItem *ni = &_news_items[getNews(p)];
    00719
    00720       SetDParam(0, ni->date);
    00721       DrawString(4, y, STR_SHORT_DATE, TC_WHITE);
    00722
    00723       DrawNewsString(82, y, TC_WHITE, ni, w->width - 95);
    00724       y += 12;
    00725     }
    00726     break;
    00727   }
    00728
    00729   case WE_CLICK:
    00730     switch (e->we.click.widget) {
    00731     case 3: {
    00732       int y = (e->we.click.pt.y - 19) / 12;
    00733       NewsID p = getNews(y + w->vscroll.pos);
    00734
    00735       if (p == INVALID_NEWS) break;
    00736
    00737       ShowNewsMessage(p);
    00738       break;
    00739     }
    00740     }
    00741     break;
    00742
    00743   case WE_RESIZE:
    00744     w->vscroll.cap += e->we.sizing.diff.y / 12;
    00745     break;
    00746   }
    00747 }
    00748
    00749 static const Widget _message_history_widgets[] = {
    00750 {   WWT_CLOSEBOX,   RESIZE_NONE,    13,     0,    10,     0,    13, STR_00C5,            STR_018B_CLOSE_WINDOW},
    00751 {    WWT_CAPTION,  RESIZE_RIGHT,    13,    11,   387,     0,    13, STR_MESSAGE_HISTORY, STR_018C_WINDOW_TITLE_DRAG_THIS},
    00752 {  WWT_STICKYBOX,     RESIZE_LR,    13,   388,   399,     0,    13, 0x0,                 STR_STICKY_BUTTON},
    00753 {      WWT_PANEL,     RESIZE_RB,    13,     0,   387,    14,   139, 0x0,                 STR_MESSAGE_HISTORY_TIP},
    00754 {  WWT_SCROLLBAR,    RESIZE_LRB,    13,   388,   399,    14,   127, 0x0,                 STR_0190_SCROLL_BAR_SCROLLS_LIST},
    00755 {  WWT_RESIZEBOX,   RESIZE_LRTB,    13,   388,   399,   128,   139, 0x0,                 STR_RESIZE_BUTTON},
    00756 {   WIDGETS_END},
    00757 };
    00758
    00759 static const WindowDesc _message_history_desc = {
    00760   240, 22, 400, 140, 400, 140,
    00761   WC_MESSAGE_HISTORY, WC_NONE,
    00762   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
    00763   _message_history_widgets,
    00764   MessageHistoryWndProc
    00765 };
    00766
    00768 void ShowMessageHistory()
    00769 {
    00770   Window *w;
    00771
    00772   DeleteWindowById(WC_MESSAGE_HISTORY, 0);
    00773   w = AllocateWindowDesc(&_message_history_desc);
    00774
    00775   if (w != NULL) {
    00776     w->vscroll.cap = 10;
    00777     w->vscroll.count = _total_news;
    00778     w->resize.step_height = 12;
    00779     w->resize.height = w->height - 12 * 6; // minimum of 4 items in the list, each item 12 high
    00780     w->resize.step_width = 1;
    00781     w->resize.width = 200; // can't make window any smaller than 200 pixel
    00782     SetWindowDirty(w);
    00783   }
    00784 }
    00785
    00786
    00788 enum {
    00789   WIDGET_NEWSOPT_DROP_SUMMARY = 4,
    00790   WIDGET_NEWSOPT_SOUNDTICKER  = 6,
    00791   WIDGET_NEWSOPT_START_OPTION = 8,
    00792 };
    00793
    00803 static void SetMessageButtonStates(Window *w, byte value, int element)
    00804 {
    00805   element *= NB_WIDG_PER_SETTING;
    00806
    00807   w->SetWidgetDisabledState(element + WIDGET_NEWSOPT_START_OPTION, value == 0);
    00808   w->SetWidgetDisabledState(element + WIDGET_NEWSOPT_START_OPTION + 2, value == 2);
    00809 }
    00810
    00816 static void MessageOptionsWndProc(Window *w, WindowEvent *e)
    00817 {
    00818   static const StringID message_opt[] = {STR_OFF, STR_SUMMARY, STR_FULL, INVALID_STRING_ID};
    00819
    00820   /* WP(w, def_d).data_1 stores state of the ALL on/off/summary button */
    00821   switch (e->event) {
    00822     case WE_CREATE: {
    00823       uint32 val = _news_display_opt;
    00824       uint32 all_val;
    00825       int i;
    00826
    00827       /* Set up the initial disabled buttons in the case of 'off' or 'full' */
    00828       all_val = val & 0x3;
    00829       for (i = 0; i < NT_END; i++, val >>= 2) {
    00830         SetMessageButtonStates(w, val & 0x3, i);
    00831         /* If the value doesn't match the ALL-button value, set the ALL-button value to 'off' */
    00832         if ((val & 0x3) != all_val) all_val = 0;
    00833       }
    00834       /* If all values are the same value, the ALL-button will take over this value */
    00835       WP(w, def_d).data_1 = all_val;
    00836     } break;
    00837
    00838     case WE_PAINT: {
    00839       uint32 val = _news_display_opt;
    00840       int i, y;
    00841
    00842       if (_news_ticker_sound) w->LowerWidget(WIDGET_NEWSOPT_SOUNDTICKER);
    00843
    00844       w->widget[WIDGET_NEWSOPT_DROP_SUMMARY].data = message_opt[WP(w, def_d).data_1];
    00845       DrawWindowWidgets(w);
    00846
    00847       /* Draw the string of each setting on each button. */
    00848       for (i = 0, y = 26; i < NT_END; i++, y += 12, val >>= 2) {
    00849         /* 51 comes from 13 + 89 (left and right of the button)+1, shiefted by one as to get division,
    00850          * which will give centered position */
    00851         DrawStringCentered(51, y + 1, message_opt[val & 0x3], TC_BLACK);
    00852       }
    00853     } break;
    00854
    00855     case WE_CLICK:
    00856       switch (e->we.click.widget) {
    00857         case WIDGET_NEWSOPT_DROP_SUMMARY: // Dropdown menu for all settings
    00858           ShowDropDownMenu(w, message_opt, WP(w, def_d).data_1, WIDGET_NEWSOPT_DROP_SUMMARY, 0, 0);
    00859           break;
    00860
    00861         case WIDGET_NEWSOPT_SOUNDTICKER: // Change ticker sound on/off
    00862           _news_ticker_sound ^= 1;
    00863           w->ToggleWidgetLoweredState(e->we.click.widget);
    00864           w->InvalidateWidget(e->we.click.widget);
    00865           break;
    00866
    00867         default: { // Clicked on the [<] .. [>] widgets
    00868           int wid = e->we.click.widget - WIDGET_NEWSOPT_START_OPTION;
    00869           if (wid >= 0 && wid < (NB_WIDG_PER_SETTING * NT_END)) {
    00870             int element = wid / NB_WIDG_PER_SETTING;
    00871             byte val = (GetNewsDisplayValue(element) + ((wid % NB_WIDG_PER_SETTING) ? 1 : -1)) % 3;
    00872
    00873             SetMessageButtonStates(w, val, element);
    00874             SetNewsDisplayValue(element, val);
    00875             SetWindowDirty(w);
    00876           }
    00877         } break;
    00878       } break;
    00879
    00880     case WE_DROPDOWN_SELECT: { // Select all settings for newsmessages
    00881       int i;
    00882
    00883       WP(w, def_d).data_1 = e->we.dropdown.index;
    00884
    00885       for (i = 0; i < NT_END; i++) {
    00886         SetMessageButtonStates(w, e->we.dropdown.index, i);
    00887         SetNewsDisplayValue(i, e->we.dropdown.index);
    00888       }
    00889       SetWindowDirty(w);
    00890     } break;
    00891   }
    00892 }
    00893
    00894
    00895 /*
    00896 * The news settings window widgets
    00897 *
    00898 * Main part of the window is a list of news-setting lines, one for each news category.
    00899 * Each line is constructed by an expansion of the \c NEWS_SETTINGS_LINE macro
    00900 */
    00901
    00918 #define NEWS_SETTINGS_LINE(basey, linenum, text) \
    00919   { WWT_PUSHIMGBTN, RESIZE_NONE, COLOUR_YELLOW, \
    00920       4,  12,  basey     + linenum * NEWS_SETTING_BASELINE_SKIP,  basey + 11 + linenum * NEWS_SETTING_BASELINE_SKIP, \
    00921     SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, \
    00922   { WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_YELLOW, \
    00923      13,  89,  basey     + linenum * NEWS_SETTING_BASELINE_SKIP,  basey + 11 + linenum * NEWS_SETTING_BASELINE_SKIP, \
    00924     STR_EMPTY, STR_NULL}, \
    00925   { WWT_PUSHIMGBTN, RESIZE_NONE, COLOUR_YELLOW, \
    00926      90,  98,  basey     + linenum * NEWS_SETTING_BASELINE_SKIP,  basey + 11 + linenum * NEWS_SETTING_BASELINE_SKIP, \
    00927     SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, \
    00928         { WWT_TEXT, RESIZE_NONE, COLOUR_YELLOW, \
    00929     103, 409,  basey + 1 + linenum * NEWS_SETTING_BASELINE_SKIP,  basey + 13 + linenum * NEWS_SETTING_BASELINE_SKIP, \
    00930     text, STR_NULL}
    00931
    00932 static const int NEWS_SETTING_BASELINE_SKIP = 12;
    00933
    00934
    00935 static const Widget _message_options_widgets[] = {
    00936 { WWT_CLOSEBOX, RESIZE_NONE, COLOUR_BROWN,   0,  10,  0, 13,
    00937   STR_00C5,                 STR_018B_CLOSE_WINDOW},
    00938 {  WWT_CAPTION, RESIZE_NONE, COLOUR_BROWN,  11, 409,  0, 13,
    00939   STR_0204_MESSAGE_OPTIONS, STR_018C_WINDOW_TITLE_DRAG_THIS},
    00940 {    WWT_PANEL, RESIZE_NONE, COLOUR_BROWN,   0, 409, 14, 64 + NT_END * NEWS_SETTING_BASELINE_SKIP,
    00941   0x0,                      STR_NULL},
    00942
    00943 /* Text at the top of the main panel, in black */
    00944 {    WWT_LABEL, RESIZE_NONE, COLOUR_BROWN,
    00945     0, 409, 13, 26,
    00946   STR_0205_MESSAGE_TYPES,   STR_NULL},
    00947
    00948 /* General drop down and sound button, widgets WIDGET_NEWSOPT_BTN_SUMMARY and WIDGET_NEWSOPT_DROP_SUMMARY */
    00949 {  WWT_DROPDOWN, RESIZE_NONE, COLOUR_YELLOW,
    00950     4,  98,  34 + NT_END * NEWS_SETTING_BASELINE_SKIP,  45 + NT_END * NEWS_SETTING_BASELINE_SKIP,
    00951   0x0, STR_NULL},
    00952
    00953 {      WWT_TEXT, RESIZE_NONE, COLOUR_YELLOW,
    00954   103, 409,  35 + NT_END * NEWS_SETTING_BASELINE_SKIP,  47 + NT_END * NEWS_SETTING_BASELINE_SKIP,
    00955   STR_MESSAGES_ALL, STR_NULL},
    00956
    00957 /* Below is widget WIDGET_NEWSOPT_SOUNDTICKER */
    00958 { WWT_TEXTBTN_2, RESIZE_NONE, COLOUR_YELLOW,
    00959     4,  98,  46 + NT_END * NEWS_SETTING_BASELINE_SKIP,  57 + NT_END * NEWS_SETTING_BASELINE_SKIP,
    00960   STR_02DB_OFF,  STR_NULL},
    00961
    00962 {      WWT_TEXT, RESIZE_NONE, COLOUR_YELLOW,
    00963   103, 409,  47 + NT_END * NEWS_SETTING_BASELINE_SKIP,  59 + NT_END * NEWS_SETTING_BASELINE_SKIP,
    00964   STR_MESSAGE_SOUND, STR_NULL},
    00965
    00966 /* List of news-setting lines (4 widgets for each line).
    00967  * First widget must be number WIDGET_NEWSOPT_START_OPTION
    00968  */
    00969 NEWS_SETTINGS_LINE(26, NT_ARRIVAL_PLAYER, STR_0206_ARRIVAL_OF_FIRST_VEHICLE),
    00970 NEWS_SETTINGS_LINE(26, NT_ARRIVAL_OTHER,  STR_0207_ARRIVAL_OF_FIRST_VEHICLE),
    00971 NEWS_SETTINGS_LINE(26, NT_ACCIDENT, STR_0208_ACCIDENTS_DISASTERS),
    00972 NEWS_SETTINGS_LINE(26, NT_COMPANY_INFO, STR_0209_COMPANY_INFORMATION),
    00973 NEWS_SETTINGS_LINE(26, NT_OPENCLOSE, STR_NEWS_OPEN_CLOSE),
    00974 NEWS_SETTINGS_LINE(26, NT_ECONOMY, STR_020A_ECONOMY_CHANGES),
    00975 NEWS_SETTINGS_LINE(26, NT_INDUSTRY_PLAYER, STR_INDUSTRY_CHANGES_SERVED_BY_PLAYER),
    00976 NEWS_SETTINGS_LINE(26, NT_INDUSTRY_OTHER, STR_INDUSTRY_CHANGES_SERVED_BY_OTHER),
    00977 NEWS_SETTINGS_LINE(26, NT_INDUSTRY_NOBODY, STR_OTHER_INDUSTRY_PRODUCTION_CHANGES),
    00978 NEWS_SETTINGS_LINE(26, NT_ADVICE, STR_020B_ADVICE_INFORMATION_ON_PLAYER),
    00979 NEWS_SETTINGS_LINE(26, NT_NEW_VEHICLES, STR_020C_NEW_VEHICLES),
    00980 NEWS_SETTINGS_LINE(26, NT_ACCEPTANCE, STR_020D_CHANGES_OF_CARGO_ACCEPTANCE),
    00981 NEWS_SETTINGS_LINE(26, NT_SUBSIDIES, STR_020E_SUBSIDIES),
    00982 NEWS_SETTINGS_LINE(26, NT_GENERAL, STR_020F_GENERAL_INFORMATION),
    00983
    00984 {   WIDGETS_END},
    00985 };
    00986
    00987 static const WindowDesc _message_options_desc = {
    00988   270,  22,  410,  65 + NT_END * NEWS_SETTING_BASELINE_SKIP,
    00989              410,  65 + NT_END * NEWS_SETTING_BASELINE_SKIP,
    00990   WC_GAME_OPTIONS, WC_NONE,
    00991   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
    00992   _message_options_widgets,
    00993   MessageOptionsWndProc
    00994 };
    00995
    00996 void ShowMessageOptions()
    00997 {
    00998   DeleteWindowById(WC_GAME_OPTIONS, 0);
    00999   AllocateWindowDesc(&_message_options_desc);
    01000 }
    01001
    01002
    01003 void DeleteVehicleNews(VehicleID vid, StringID news)
    01004 {
    01005   NewsID n;
    01006
    01007   for (n = _oldest_news; _latest_news != INVALID_NEWS; n = increaseIndex(n)) {
    01008     const NewsItem *ni = &_news_items[n];
    01009
    01010     if (ni->flags & NF_VEHICLE &&
    01011         ni->data_a == vid &&
    01012         (news == INVALID_STRING_ID || ni->string_id == news)) {
    01013       Window *w;
    01014
    01015       /* If we delete a forced news and it is just before the current news
    01016        * then we need to advance to the next news (if any) */
    01017       if (_forced_news == n) MoveToNextItem();
    01018       if (_forced_news == INVALID_NEWS && _current_news == n) MoveToNextItem();
    01019       _total_news--;
    01020
    01021       /* If this is the last news item, invalidate _latest_news */
    01022       if (_total_news == 0) {
    01023         assert(_latest_news == _oldest_news);
    01024         _latest_news = INVALID_NEWS;
    01025         _current_news = INVALID_NEWS;
    01026       }
    01027
    01028       /* Since we only imitate a FIFO removing an arbitrary element does need
    01029        * some magic. Remove the item by shifting head towards the tail. eg
    01030        *    oldest    remove  last
    01031        *        |        |     |
    01032        * [------O--------n-----L--]
    01033        * will become (change dramatized to make clear)
    01034        * [---------O-----------L--]
    01035        * We also need an update of the current, forced and visible (open window)
    01036        * news's as this shifting could change the items they were pointing to */
    01037       if (_total_news != 0) {
    01038         w = FindWindowById(WC_NEWS_WINDOW, 0);
    01039         NewsID visible_news = (w != NULL) ? (NewsID)(WP(w, news_d).ni - _news_items) : INVALID_NEWS;
    01040
    01041         for (NewsID i = n;; i = decreaseIndex(i)) {
    01042           _news_items[i] = _news_items[decreaseIndex(i)];
    01043
    01044           if (i != _latest_news) {
    01045             if (i == _current_news) _current_news = increaseIndex(_current_news);
    01046             if (i == _forced_news) _forced_news = increaseIndex(_forced_news);
    01047             if (i == visible_news) WP(w, news_d).ni = &_news_items[increaseIndex(visible_news)];
    01048           }
    01049
    01050           if (i == _oldest_news) break;
    01051         }
    01052         _oldest_news = increaseIndex(_oldest_news);
    01053       }
    01054
    01055       /*DEBUG(misc, 0, "-cur %3d, old %2d, lat %3d, for %3d, tot %2d",
    01056         _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/
    01057
    01058       w = FindWindowById(WC_MESSAGE_HISTORY, 0);
    01059       if (w != NULL) {
    01060         SetWindowDirty(w);
    01061         w->vscroll.count = _total_news;
    01062       }
    01063     }
    01064
    01065     if (n == _latest_news) break;
    01066   }
    01067 }