truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" tron@1309: #include "strings.h" Darkvater@1688: #include "table/sprites.h" tron@507: #include "table/strings.h" truelight@0: #include "window.h" truelight@0: #include "gui.h" truelight@0: #include "viewport.h" truelight@0: #include "gfx.h" truelight@0: #include "news.h" truelight@0: #include "vehicle.h" tron@337: #include "sound.h" truelight@0: dominik@79: /* News system dominik@79: News system is realized as a FIFO queue (in an array) dominik@79: The positions in the queue can't be rearranged, we only access dominik@79: the array elements through pointers to the elements. Once the truelight@193: array is full, the oldest entry (_oldest_news) is being overwritten dominik@79: by the newest (_latest news). truelight@0: dominik@79: oldest current lastest dominik@79: | | | dominik@79: [O------------F-------------C---------L ] dominik@79: | dominik@79: forced dominik@79: */ dominik@79: tron@427: #define MAX_NEWS 30 tron@427: tron@427: #define INVALID_NEWS 255 dominik@79: dominik@79: static NewsItem _news_items[MAX_NEWS]; tron@427: static byte _current_news = INVALID_NEWS; // points to news item that should be shown next dominik@79: static byte _oldest_news = 0; // points to first item in fifo queue tron@427: static byte _latest_news = INVALID_NEWS; // points to last item in fifo queue tron@427: /* if the message being shown was forced by the user, its index is stored in tron@427: * _forced_news. forced_news is INVALID_NEWS otherwise. tron@427: * (Users can force messages through history or "last message") */ tron@427: static byte _forced_news = INVALID_NEWS; dominik@79: dominik@79: static byte _total_news = 0; // total news count truelight@0: truelight@0: void DrawNewsNewTrainAvail(Window *w); truelight@0: void DrawNewsNewRoadVehAvail(Window *w); truelight@0: void DrawNewsNewShipAvail(Window *w); truelight@0: void DrawNewsNewAircraftAvail(Window *w); truelight@0: void DrawNewsBankrupcy(Window *w); tron@427: static void MoveToNexItem(void); truelight@0: truelight@0: StringID GetNewsStringNewTrainAvail(NewsItem *ni); truelight@0: StringID GetNewsStringNewRoadVehAvail(NewsItem *ni); truelight@0: StringID GetNewsStringNewShipAvail(NewsItem *ni); truelight@0: StringID GetNewsStringNewAircraftAvail(NewsItem *ni); truelight@0: StringID GetNewsStringBankrupcy(NewsItem *ni); truelight@0: truelight@0: static DrawNewsCallbackProc * const _draw_news_callback[] = { tron@427: DrawNewsNewTrainAvail, /* DNC_TRAINAVAIL */ tron@427: DrawNewsNewRoadVehAvail, /* DNC_ROADAVAIL */ tron@427: DrawNewsNewShipAvail, /* DNC_SHIPAVAIL */ truelight@0: DrawNewsNewAircraftAvail, /* DNC_AIRCRAFTAVAIL */ tron@427: DrawNewsBankrupcy, /* DNC_BANKRUPCY */ truelight@0: }; truelight@0: truelight@0: GetNewsStringCallbackProc * const _get_news_string_callback[] = { tron@427: GetNewsStringNewTrainAvail, /* DNC_TRAINAVAIL */ tron@427: GetNewsStringNewRoadVehAvail, /* DNC_ROADAVAIL */ tron@427: GetNewsStringNewShipAvail, /* DNC_SHIPAVAIL */ truelight@0: GetNewsStringNewAircraftAvail, /* DNC_AIRCRAFTAVAIL */ tron@427: GetNewsStringBankrupcy, /* DNC_BANKRUPCY */ truelight@0: }; truelight@0: tron@427: void InitNewsItemStructs(void) dominik@79: { tron@427: memset(_news_items, 0, sizeof(_news_items)); tron@427: _current_news = INVALID_NEWS; dominik@89: _oldest_news = 0; tron@427: _latest_news = INVALID_NEWS; tron@427: _forced_news = INVALID_NEWS; dominik@89: _total_news = 0; dominik@79: } truelight@0: tron@427: void DrawNewsBorder(const Window *w) truelight@0: { truelight@0: int left = 0; truelight@0: int right = w->width - 1; truelight@0: int top = 0; truelight@0: int bottom = w->height - 1; truelight@0: truelight@0: GfxFillRect(left, top, right, bottom, 0xF); truelight@193: truelight@0: GfxFillRect(left, top, left, bottom, 0xD7); truelight@0: GfxFillRect(right, top, right, bottom, 0xD7); truelight@0: GfxFillRect(left, top, right, top, 0xD7); truelight@0: GfxFillRect(left, bottom, right, bottom, 0xD7); truelight@193: truelight@193: DrawString(left + 2, top + 1, STR_00C6, 0); truelight@0: } truelight@0: truelight@0: static void NewsWindowProc(Window *w, WindowEvent *e) truelight@0: { tron@427: switch (e->event) { darkvater@1648: case WE_CREATE: { /* If chatbar is open at creation time, we need to go above it */ darkvater@1648: const Window *w1 = FindWindowById(WC_SEND_NETWORK_MSG, 0); darkvater@1648: w->message.msg = (w1 != NULL) ? w1->height : 0; darkvater@1648: } break; darkvater@1648: truelight@0: case WE_PAINT: { tron@427: const NewsItem *ni = WP(w, news_d).ni; truelight@0: ViewPort *vp; truelight@0: tron@427: switch (ni->display_mode) { tron@427: case NM_NORMAL: tron@427: case NM_THIN: { tron@427: DrawNewsBorder(w); truelight@0: tron@427: DrawString(2, 1, STR_00C6, 0); dominik@79: tron@534: SetDParam(0, ni->date); tron@427: DrawStringRightAligned(428, 1, STR_01FF, 0); tron@427: tron@427: if (!(ni->flags & NF_VIEWPORT)) { tron@427: COPY_IN_DPARAM(0, ni->params, lengthof(ni->params)); tron@427: DrawStringMultiCenter(215, ni->display_mode == NM_NORMAL ? 76 : 56, tron@427: ni->string_id, 426); tron@427: } else { tron@427: byte bk = _display_opt; tron@497: _display_opt &= ~DO_TRANS_BUILDINGS; tron@427: DrawWindowViewport(w); tron@427: _display_opt = bk; tron@427: tron@427: /* Shade the viewport into gray, or color*/ tron@427: vp = w->viewport; tron@427: GfxFillRect(vp->left - w->left, vp->top - w->top, tron@427: vp->left - w->left + vp->width - 1, vp->top - w->top + vp->height - 1, tron@427: ni->flags & NF_INCOLOR ? 0x4322 : 0x4323 tron@427: ); tron@427: tron@427: COPY_IN_DPARAM(0, ni->params, lengthof(ni->params)); tron@427: DrawStringMultiCenter(w->width / 2, 20, ni->string_id, 428); tron@427: } tron@427: break; truelight@0: } tron@427: tron@427: case NM_CALLBACK: { tron@427: _draw_news_callback[ni->callback](w); tron@427: break; tron@427: } tron@427: tron@427: default: { tron@427: DrawWindowWidgets(w); tron@427: if (!(ni->flags & NF_VIEWPORT)) { tron@427: COPY_IN_DPARAM(0, ni->params, lengthof(ni->params)); tron@427: DrawStringMultiCenter(140, 38, ni->string_id, 276); tron@427: } else { tron@427: DrawWindowViewport(w); tron@427: COPY_IN_DPARAM(0, ni->params, lengthof(ni->params)); tron@427: DrawStringMultiCenter(w->width / 2, w->height - 16, ni->string_id, 276); tron@427: } tron@427: break; truelight@0: } truelight@0: } truelight@0: } break; truelight@0: truelight@0: case WE_CLICK: { tron@427: switch (e->click.widget) { dominik@79: case 1: { tron@427: NewsItem *ni = WP(w, news_d).ni; truelight@80: DeleteWindow(w); dominik@79: ni->duration = 0; tron@427: _forced_news = INVALID_NEWS; dominik@79: } break; truelight@0: case 0: { tron@427: NewsItem *ni = WP(w, news_d).ni; truelight@0: if (ni->flags & NF_VEHICLE) { truelight@919: Vehicle *v = GetVehicle(ni->data_a); truelight@0: ScrollMainWindowTo(v->x_pos, v->y_pos); truelight@0: } else if (ni->flags & NF_TILE) { truelight@0: if (!ScrollMainWindowToTile(ni->data_a) && ni->data_b != 0) truelight@0: ScrollMainWindowToTile(ni->data_b); truelight@0: } truelight@0: } break; truelight@0: } truelight@0: } break; truelight@0: truelight@0: case WE_KEYPRESS: truelight@0: if (e->keypress.keycode == WKC_SPACE) { truelight@0: // Don't continue. truelight@0: e->keypress.cont = false; truelight@0: DeleteWindow(w); truelight@0: } truelight@0: break; truelight@0: darkvater@1648: case WE_MESSAGE: /* The chatbar has notified us that is was either created or closed */ darkvater@1648: switch (e->message.msg) { darkvater@1648: case WE_CREATE: w->message.msg = e->message.wparam; break; darkvater@1648: case WE_DESTROY: w->message.msg = 0; break; darkvater@1648: } darkvater@1648: break; darkvater@1648: darkvater@1648: case WE_TICK: { /* Scroll up newsmessages from the bottom in steps of 4 pixels */ darkvater@1648: int diff; darkvater@1648: int y = max(w->top - 4, _screen.height - w->height - 12 - w->message.msg); darkvater@1648: if (y == w->top) return; truelight@0: truelight@0: if (w->viewport != NULL) truelight@0: w->viewport->top += y - w->top; truelight@0: darkvater@1648: diff = abs(w->top - y); truelight@0: w->top = y; truelight@0: darkvater@1648: SetDirtyBlocks(w->left, w->top - diff, w->left + w->width, w->top + w->height); truelight@0: } break; truelight@0: } truelight@0: } truelight@0: dominik@79: // returns the correct index in the array dominik@79: // (to deal with overflows) tron@1095: static byte increaseIndex(byte i) dominik@79: { tron@427: if (i == INVALID_NEWS) dominik@83: return 0; dominik@83: i++; tron@427: if (i >= MAX_NEWS) dominik@83: i = i % MAX_NEWS; dominik@79: return i; dominik@79: } dominik@79: truelight@0: void AddNewsItem(StringID string, uint32 flags, uint data_a, uint data_b) truelight@0: { truelight@0: NewsItem *ni; dominik@79: Window *w; truelight@0: truelight@0: if (_game_mode == GM_MENU) truelight@0: return; truelight@0: dominik@102: // check the rare case that the oldest (to be overwritten) news item is open dominik@935: if (_total_news==MAX_NEWS && (_oldest_news == _current_news || _oldest_news == _forced_news)) dominik@102: MoveToNexItem(); dominik@102: tron@427: _forced_news = INVALID_NEWS; tron@427: if (_total_news < MAX_NEWS) _total_news++; truelight@193: dominik@79: // make sure our pointer isn't overflowing dominik@83: _latest_news = increaseIndex(_latest_news); dominik@79: dominik@79: // overwrite oldest news entry tron@427: if (_oldest_news == _latest_news && _news_items[_oldest_news].string_id != 0) dominik@83: _oldest_news = increaseIndex(_oldest_news); // but make sure we're not overflowing here dominik@79: dominik@79: // add news to _latest_news dominik@79: ni = &_news_items[_latest_news]; truelight@745: memset(ni, 0, sizeof(*ni)); dominik@79: dominik@79: ni->string_id = string; dominik@79: ni->display_mode = (byte)flags; dominik@79: ni->flags = (byte)(flags >> 8) | NF_NOEXPIRE; dominik@79: dominik@79: // show this news message in color? dominik@79: if (_date >= ConvertIntDate(_patches.colored_news_date)) dominik@79: ni->flags |= NF_INCOLOR; dominik@79: dominik@79: ni->type = (byte)(flags >> 16); dominik@79: ni->callback = (byte)(flags >> 24); dominik@79: ni->data_a = data_a; dominik@79: ni->data_b = data_b; dominik@79: ni->date = _date; dominik@79: COPY_OUT_DPARAM(ni->params, 0, lengthof(ni->params)); dominik@79: dominik@79: w = FindWindowById(WC_MESSAGE_HISTORY, 0); tron@427: if (w == NULL) return; dominik@79: SetWindowDirty(w); dominik@79: w->vscroll.count = _total_news; truelight@0: } truelight@0: dominik@715: /* To add a news item with an attached validation function. This validation function dominik@715: * makes sure that the news item is not outdated when the newspaper pops up. */ dominik@718: void AddValidatedNewsItem(StringID string, uint32 flags, uint data_a, uint data_b, ValidationProc *validation) dominik@715: { dominik@715: AddNewsItem(string, flags, data_a, data_b); dominik@715: _news_items[_latest_news].isValid = validation; dominik@715: } dominik@715: dominik@79: // don't show item if it's older than x days truelight@0: static const byte _news_items_age[] = {60, 60, 90, 60, 90, 30, 150, 30, 90, 180}; truelight@0: truelight@0: static const Widget _news_type13_widgets[] = { truelight@867: { WWT_PANEL, RESIZE_NONE, 15, 0, 429, 0, 169, 0x0, STR_NULL}, truelight@867: { WWT_PANEL, RESIZE_NONE, 15, 0, 10, 0, 11, 0x0, STR_NULL}, darkvater@176: { WIDGETS_END}, truelight@0: }; truelight@0: truelight@0: static WindowDesc _news_type13_desc = { truelight@0: WDP_CENTER, 476, 430, 170, tron@427: WC_NEWS_WINDOW, 0, truelight@0: WDF_DEF_WIDGET, truelight@0: _news_type13_widgets, truelight@0: NewsWindowProc truelight@0: }; truelight@0: truelight@0: static const Widget _news_type2_widgets[] = { truelight@867: { WWT_PANEL, RESIZE_NONE, 15, 0, 429, 0, 129, 0x0, STR_NULL}, truelight@867: { WWT_PANEL, RESIZE_NONE, 15, 0, 10, 0, 11, 0x0, STR_NULL}, darkvater@176: { WIDGETS_END}, truelight@0: }; truelight@0: truelight@0: static WindowDesc _news_type2_desc = { truelight@0: WDP_CENTER, 476, 430, 130, tron@427: WC_NEWS_WINDOW, 0, truelight@0: WDF_DEF_WIDGET, truelight@0: _news_type2_widgets, truelight@0: NewsWindowProc truelight@0: }; truelight@0: truelight@0: static const Widget _news_type0_widgets[] = { truelight@867: { WWT_PANEL, RESIZE_NONE, 5, 0, 279, 14, 86, 0x0, STR_NULL}, truelight@867: { WWT_CLOSEBOX, RESIZE_NONE, 5, 0, 10, 0, 13, STR_00C5, STR_NULL}, truelight@867: { WWT_CAPTION, RESIZE_NONE, 5, 11, 279, 0, 13, STR_012C_MESSAGE, STR_NULL}, truelight@867: { WWT_6, RESIZE_NONE, 5, 2, 277, 16, 64, 0x0, STR_NULL}, darkvater@176: { WIDGETS_END}, truelight@0: }; truelight@0: truelight@0: static WindowDesc _news_type0_desc = { truelight@0: WDP_CENTER, 476, 280, 87, tron@427: WC_NEWS_WINDOW, 0, truelight@0: WDF_DEF_WIDGET, truelight@0: _news_type0_widgets, truelight@0: NewsWindowProc truelight@0: }; truelight@0: tron@427: static const SoundFx _news_sounds[] = { tron@427: SND_1D_APPLAUSE, tron@427: SND_1D_APPLAUSE, tron@427: 0, tron@427: 0, tron@427: 0, tron@427: 0, tron@427: SND_1E_OOOOH, tron@427: 0, tron@427: 0, tron@427: 0 tron@427: }; truelight@0: Darkvater@1688: /** Get the value of an item of the news-display settings. This is Darkvater@1688: * a little tricky since on/off/summary must use 2 bits to store the value Darkvater@1688: * @param item the item whose value is requested Darkvater@1688: * @return return the found value which is between 0-2 Darkvater@1688: */ Darkvater@1688: static inline byte GetNewsDisplayValue(byte item) Darkvater@1688: { Darkvater@1688: assert(item < 10 && ((_news_display_opt >> (item * 2)) & 0x3) <= 2); Darkvater@1688: return (_news_display_opt >> (item * 2)) & 0x3; Darkvater@1688: } Darkvater@1688: Darkvater@1688: /** Set the value of an item in the news-display settings. This is Darkvater@1688: * a little tricky since on/off/summary must use 2 bits to store the value Darkvater@1688: * @param item the item whose value is being set Darkvater@1688: * @param val new value Darkvater@1688: */ Darkvater@1688: static inline void SetNewsDisplayValue(byte item, byte val) Darkvater@1688: { Darkvater@1688: assert(item < 10 && val <= 2); Darkvater@1688: item *= 2; Darkvater@1688: CLRBIT(_news_display_opt, item); Darkvater@1688: CLRBIT(_news_display_opt, item + 1); Darkvater@1688: _news_display_opt |= val << item; Darkvater@1688: } Darkvater@1688: dominik@79: // open up an own newspaper window for the news item dominik@79: static void ShowNewspaper(NewsItem *ni) truelight@0: { truelight@0: Window *w; truelight@0: int sound; dominik@79: int top; tron@427: ni->flags &= ~(NF_NOEXPIRE | NF_FORCE_BIG); dominik@79: ni->duration = 555; truelight@0: dominik@79: sound = _news_sounds[ni->type]; dominik@79: if (sound != 0) dominik@79: SndPlayFx(sound); dominik@79: darkvater@1648: top = _screen.height; tron@427: switch (ni->display_mode) { tron@427: case NM_NORMAL: tron@427: case NM_CALLBACK: { tron@427: _news_type13_desc.top = top; tron@427: w = AllocateWindowDesc(&_news_type13_desc); tron@427: if (ni->flags & NF_VIEWPORT) tron@427: AssignWindowViewport(w, 2, 58, 0x1AA, 0x6E, tron@427: ni->data_a | (ni->flags & NF_VEHICLE ? 0x80000000 : 0), 0); tron@427: break; dominik@79: } tron@427: tron@427: case NM_THIN: { tron@427: _news_type2_desc.top = top; tron@427: w = AllocateWindowDesc(&_news_type2_desc); tron@427: if (ni->flags & NF_VIEWPORT) tron@427: AssignWindowViewport(w, 2, 58, 0x1AA, 0x46, tron@427: ni->data_a | (ni->flags & NF_VEHICLE ? 0x80000000 : 0), 0); tron@427: break; dominik@79: } tron@427: tron@427: default: { tron@427: _news_type0_desc.top = top; tron@427: w = AllocateWindowDesc(&_news_type0_desc); tron@427: if (ni->flags & NF_VIEWPORT) tron@427: AssignWindowViewport(w, 3, 17, 0x112, 0x2F, tron@427: ni->data_a | (ni->flags & NF_VEHICLE ? 0x80000000 : 0), 0); tron@427: break; dominik@79: } truelight@0: } tron@427: WP(w, news_d).ni = &_news_items[_forced_news == INVALID_NEWS ? _current_news : _forced_news]; dominik@79: w->flags4 |= WF_DISABLE_VP_SCROLL; dominik@79: } truelight@0: dominik@79: // show news item in the ticker tron@427: static void ShowTicker(const NewsItem *ni) dominik@79: { dominik@79: Window *w; dominik@79: Darkvater@1688: if (_news_ticker_sound) SndPlayFx(SND_16_MORSE); Darkvater@1688: dominik@79: _statusbar_news_item = *ni; dominik@79: w = FindWindowById(WC_STATUS_BAR, 0); tron@427: if (w != NULL) tron@427: WP(w, def_d).data_1 = 360; dominik@79: } dominik@79: dominik@79: dominik@79: // Are we ready to show another news item? dominik@79: // Only if nothing is in the newsticker and no newspaper is displayed tron@427: static bool ReadyForNextItem(void) dominik@79: { tron@427: const Window *w; tron@427: byte item = _forced_news == INVALID_NEWS ? _current_news : _forced_news; dominik@92: NewsItem *ni; dominik@92: tron@427: if (item >= MAX_NEWS) return true; dominik@92: ni = &_news_items[item]; dominik@79: dominik@79: // Ticker message dominik@79: // Check if the status bar message is still being displayed? dominik@79: w = FindWindowById(WC_STATUS_BAR, 0); tron@427: if (w != NULL && WP(w, def_d).data_1 > -1280) dominik@79: return false; truelight@0: dominik@79: // Newspaper message dominik@79: // Wait until duration reaches 0 dominik@79: if (ni->duration != 0) { dominik@79: ni->duration--; dominik@79: return false; dominik@79: } truelight@0: dominik@79: // neither newsticker nor newspaper are running dominik@79: return true; dominik@79: } dominik@79: tron@427: static void MoveToNexItem(void) dominik@79: { dominik@79: DeleteWindowById(WC_NEWS_WINDOW, 0); tron@427: _forced_news = INVALID_NEWS; dominik@79: dominik@79: // if we're not at the last item, than move on tron@427: if (_current_news != _latest_news) { dominik@79: NewsItem *ni; dominik@79: dominik@83: _current_news = increaseIndex(_current_news); dominik@79: ni = &_news_items[_current_news]; dominik@79: dominik@79: // check the date, don't show too old items tron@427: if (_date - _news_items_age[ni->type] > ni->date) dominik@79: return; dominik@79: dominik@715: // execute the validation function to see if this item is still valid dominik@715: if ( ni->isValid != NULL && !ni->isValid(ni->data_a, ni->data_b) ) dominik@715: return; dominik@715: Darkvater@1688: switch (GetNewsDisplayValue(ni->type)) { Darkvater@1688: case 0: { /* Off - show nothing only a small reminder in the status bar */ Darkvater@1688: Window *w = FindWindowById(WC_STATUS_BAR, 0); Darkvater@1688: if (w != NULL) { Darkvater@1688: WP(w, def_d).data_2 = 91; Darkvater@1688: SetWindowDirty(w); Darkvater@1688: } Darkvater@1688: } break; Darkvater@1688: case 1: /* Summary - show ticker, but if forced big, cascade to full */ Darkvater@1688: if (!(ni->flags & NF_FORCE_BIG)) { Darkvater@1688: ShowTicker(ni); Darkvater@1688: break; Darkvater@1688: } Darkvater@1688: /* Fallthrough */ Darkvater@1688: case 2: /* Full - show newspaper*/ Darkvater@1688: ShowNewspaper(ni); Darkvater@1688: break; Darkvater@1688: } truelight@0: } dominik@79: } truelight@0: tron@427: void NewsLoop(void) truelight@0: { dominik@79: // no news item yet tron@427: if (_total_news == 0) return; dominik@79: tron@427: if (ReadyForNextItem()) dominik@79: MoveToNexItem(); dominik@79: } dominik@79: dominik@79: /* Do a forced show of a specific message */ tron@1095: static void ShowNewsMessage(byte i) dominik@79: { tron@427: if (_total_news == 0) return; dominik@89: dominik@79: // Delete the news window dominik@79: DeleteWindowById(WC_NEWS_WINDOW, 0); dominik@79: dominik@79: // setup forced news item dominik@79: _forced_news = i; dominik@79: tron@427: if (_forced_news != INVALID_NEWS) { dominik@79: NewsItem *ni = &_news_items[_forced_news]; dominik@79: ni->duration = 555; dominik@79: ni->flags |= NF_NOEXPIRE | NF_FORCE_BIG; dominik@79: DeleteWindowById(WC_NEWS_WINDOW, 0); dominik@79: ShowNewspaper(ni); dominik@79: } truelight@0: } truelight@0: tron@427: void ShowLastNewsMessage(void) truelight@0: { tron@427: if (_forced_news == INVALID_NEWS) dominik@79: ShowNewsMessage(_current_news); tron@427: else if (_forced_news != 0) tron@427: ShowNewsMessage(_forced_news - 1); dominik@83: else { tron@427: if (_total_news != MAX_NEWS) dominik@83: ShowNewsMessage(_latest_news); dominik@83: else tron@427: ShowNewsMessage(MAX_NEWS - 1); dominik@83: } truelight@0: } truelight@0: dominik@79: dominik@89: /* return news by number, with 0 being the most tron@427: recent news. Returns INVALID_NEWS if end of queue reached. */ dominik@89: static byte getNews(byte i) dominik@89: { tron@427: if (i >= _total_news) tron@427: return INVALID_NEWS; dominik@89: tron@427: if (_latest_news < i) dominik@89: i = _latest_news + MAX_NEWS - i; dominik@89: else dominik@89: i = _latest_news - i; dominik@89: tron@427: i %= MAX_NEWS; dominik@89: return i; dominik@89: } dominik@89: dominik@1097: // cut string after len pixels tron@1329: static void GetNewsString(NewsItem *ni, char *buffer, uint max) dominik@89: { tron@1336: char buf[512]; dominik@89: StringID str; tron@1329: const char *s; tron@1329: char *d; dominik@1097: uint len = 0; truelight@193: dominik@89: if (ni->display_mode == 3) { dominik@89: str = _get_news_string_callback[ni->callback](ni); dominik@89: } else { dominik@89: COPY_IN_DPARAM(0, ni->params, lengthof(ni->params)); truelight@193: str = ni->string_id; dominik@89: } dominik@89: tron@1336: GetString(buf, str); truelight@193: tron@1336: s = buf; dominik@89: d = buffer; dominik@89: tron@427: for (;; s++) { dominik@89: // cut strings that are too long dominik@1097: if (len >= max-24) { // add 3x "." at the end dominik@89: d[0] = d[1] = d[2] = '.'; tron@427: d += 3; tron@427: *d = '\0'; dominik@89: break; dominik@89: } dominik@89: tron@427: if (*s == '\0') { tron@427: *d = '\0'; dominik@89: break; tron@427: } else if (*s == '\r') { dominik@89: d[0] = d[1] = d[2] = d[3] = ' '; tron@427: d += 4; tron@1329: } else if ((byte)*s >= ' ' && ((byte)*s < 0x88 || (byte)*s >= 0x99)) { Darkvater@1390: len += GetCharacterWidth((byte)*s); dominik@89: *d++ = *s; dominik@89: } dominik@89: } dominik@89: } dominik@89: dominik@89: dominik@89: static void MessageHistoryWndProc(Window *w, WindowEvent *e) dominik@89: { tron@427: switch (e->event) { dominik@89: case WE_PAINT: { tron@427: int y = 19; dominik@89: byte p, show; dominik@89: NewsItem *ni; dominik@89: dominik@89: DrawWindowWidgets(w); dominik@89: tron@427: if (_total_news == 0) break; dominik@1097: show = min(_total_news, w->vscroll.cap); dominik@89: tron@427: for (p = w->vscroll.pos; p < w->vscroll.pos + show; p++) { tron@1329: char buffer[256]; tron@1329: dominik@89: // get news in correct order tron@427: ni = &_news_items[getNews(p)]; dominik@89: tron@534: SetDParam(0, ni->date); dominik@1097: DrawString(4, y, STR_SHORT_DATE, 16); dominik@89: dominik@1097: GetNewsString(ni, buffer, w->width-90); dominik@1097: DoDrawString(buffer, 82, y, 16); dominik@89: y += 12; dominik@89: } dominik@89: dominik@89: break; dominik@89: } dominik@89: dominik@89: case WE_CLICK: tron@427: switch (e->click.widget) { dominik@1097: case 3: { dominik@89: int y = (e->click.pt.y - 19) / 12; dominik@89: byte p, q; dominik@89: tron@427: #if 0 // === DEBUG code only tron@427: for (p = 0; p < _total_news; p++) dominik@89: { dominik@89: NewsItem *ni; dominik@89: byte buffer[256]; tron@427: ni = &_news_items[p]; dominik@89: GetNewsString(ni, buffer); dominik@89: printf("%i\t%i\t%s\n", p, ni->date, buffer); dominik@89: } dominik@89: printf("=========================\n"); tron@427: #endif dominik@89: dominik@89: p = y + w->vscroll.pos; tron@427: if (p > _total_news - 1) break; dominik@89: tron@427: if (_latest_news >= p) tron@427: q = _latest_news - p; tron@427: else tron@427: q = _latest_news + MAX_NEWS - p; dominik@89: ShowNewsMessage(q); dominik@89: dominik@89: break; dominik@89: } dominik@89: } dominik@89: break; dominik@1097: dominik@1097: case WE_RESIZE: dominik@1097: w->vscroll.cap += e->sizing.diff.y / 12; dominik@1097: break; dominik@89: } dominik@89: } dominik@89: dominik@89: static const Widget _message_history_widgets[] = { truelight@867: { WWT_CLOSEBOX, RESIZE_NONE, 13, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, dominik@1097: { WWT_CAPTION, RESIZE_RIGHT, 13, 11, 387, 0, 13, STR_MESSAGE_HISTORY, STR_018C_WINDOW_TITLE_DRAG_THIS}, dominik@1097: { WWT_STICKYBOX, RESIZE_LR, 13, 388, 399, 0, 13, 0x0, STR_STICKY_BUTTON}, dominik@1097: { WWT_IMGBTN, RESIZE_RB, 13, 0, 387, 14, 139, 0x0, STR_MESSAGE_HISTORY_TIP}, dominik@1097: { WWT_SCROLLBAR, RESIZE_LRB, 13, 388, 399, 14, 127, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, dominik@1097: { WWT_RESIZEBOX, RESIZE_LRTB, 13, 388, 399, 128, 139, 0x0, STR_RESIZE_BUTTON}, darkvater@176: { WIDGETS_END}, dominik@89: }; dominik@89: dominik@89: static const WindowDesc _message_history_desc = { dominik@89: 240, 22, 400, 140, tron@427: WC_MESSAGE_HISTORY, 0, dominik@1097: WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE, dominik@89: _message_history_widgets, dominik@89: MessageHistoryWndProc dominik@89: }; dominik@89: tron@427: void ShowMessageHistory(void) dominik@89: { dominik@89: Window *w; dominik@89: dominik@89: DeleteWindowById(WC_MESSAGE_HISTORY, 0); dominik@89: w = AllocateWindowDesc(&_message_history_desc); dominik@89: tron@427: if (w != NULL) { dominik@89: w->vscroll.cap = 10; dominik@89: w->vscroll.count = _total_news; dominik@1097: w->resize.step_height = 12; dominik@1097: w->resize.height = w->height - 12 * 6; // minimum of 4 items in the list, each item 12 high dominik@1097: w->resize.step_width = 1; dominik@1097: w->resize.width = 200; // can't make window any smaller than 200 pixel dominik@89: SetWindowDirty(w); dominik@89: } dominik@89: } dominik@89: Darkvater@1688: /** Setup the disabled/enabled buttons in the message window Darkvater@1688: * If the value is 'off' disable the [<] widget, and enable the [>] one Darkvater@1688: * Same-wise for all the others. Starting value of 3 is the first widget Darkvater@1688: * group. These are grouped as [<][>] .. [<][>], etc. Darkvater@1688: */ Darkvater@1688: static void SetMessageButtonStates(Window *w, byte value, int element) Darkvater@1688: { Darkvater@1688: element *= 2; Darkvater@1688: switch (value) { Darkvater@1688: case 0: /* Off */ Darkvater@1688: SETBIT(w->disabled_state, element + 3); Darkvater@1688: CLRBIT(w->disabled_state, element + 3 + 1); Darkvater@1688: break; Darkvater@1688: case 1: /* Summary */ Darkvater@1688: CLRBIT(w->disabled_state, element + 3); Darkvater@1688: CLRBIT(w->disabled_state, element + 3 + 1); Darkvater@1688: break; Darkvater@1688: case 2: /* Full */ Darkvater@1688: SETBIT(w->disabled_state, element + 3 + 1); Darkvater@1688: CLRBIT(w->disabled_state, element + 3); Darkvater@1688: break; Darkvater@1688: default: NOT_REACHED(); Darkvater@1688: } Darkvater@1688: } truelight@0: truelight@0: static void MessageOptionsWndProc(Window *w, WindowEvent *e) truelight@0: { Darkvater@1688: static const StringID message_opt[] = {STR_OFF, STR_SUMMARY, STR_FULL, INVALID_STRING_ID}; Darkvater@1688: static const uint32 message_val[] = {0x0, 0x55555555, 0xAAAAAAAA}; // 0x555.. = 01010101010101010101 (all summary), 286.. 1010... (full) Darkvater@1688: static const uint32 message_dis[] = { Darkvater@1688: (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9) | (1 << 11) | (1 << 13) | (1 << 15) | (1 << 17) | (1 << 19) | (1 << 21), Darkvater@1688: 0, Darkvater@1688: (1 << 4) | (1 << 6) | (1 << 8) | (1 << 10) | (1 << 12) | (1 << 14) | (1 << 16) | (1 << 18) | (1 << 20) | (1 << 22), Darkvater@1688: }; Darkvater@1688: Darkvater@1688: /* WP(w, def_d).data_1 are stores the clicked state of the fake widgets Darkvater@1688: * WP(w, def_d).data_2 stores state of the ALL on/off/summary button */ tron@427: switch (e->event) { Darkvater@1688: case WE_CREATE: { Darkvater@1688: uint32 val = _news_display_opt; Darkvater@1688: int i; Darkvater@1688: WP(w, def_d).data_1 = WP(w, def_d).data_2 = 0; Darkvater@1688: Darkvater@1688: // Set up the initial disabled buttons in the case of 'off' or 'full' Darkvater@1688: for (i = 0; i != 10; i++, val >>= 2) SetMessageButtonStates(w, val & 0x3, i); Darkvater@1688: } break; Darkvater@1688: truelight@0: case WE_PAINT: { Darkvater@1688: uint32 val = _news_display_opt; Darkvater@1688: int click_state = WP(w, def_d).data_1; truelight@0: int i, y; truelight@0: Darkvater@1688: if (_news_ticker_sound) SETBIT(w->click_state, 25); truelight@0: DrawWindowWidgets(w); truelight@0: DrawStringCentered(185, 15, STR_0205_MESSAGE_TYPES, 0); dominik@79: Darkvater@1688: /* XXX - Draw the fake widgets-buttons. Can't add these to the widget-desc since Darkvater@1688: * openttd currently can only handle 32 widgets. So hack it *g* */ Darkvater@1688: for (i = 0, y = 26; i != 10; i++, y += 12, click_state >>= 1, val >>= 2) { Darkvater@1688: bool clicked = !!(click_state & 1); Darkvater@1688: hackykid@1938: DrawFrameRect(13, y, 89, 11 + y, 3, (clicked) ? FR_LOWERED : 0); Darkvater@1688: DrawStringCentered(((13 + 89 + 1) >> 1) + clicked, ((y + 11 + y + 1) >> 1) - 5 + clicked, message_opt[val & 0x3], 0x10); Darkvater@1688: DrawString(103, y + 1, i + STR_0206_ARRIVAL_OF_FIRST_VEHICLE, 0); truelight@193: } truelight@0: Darkvater@1688: DrawString( 8, y + 9, message_opt[WP(w, def_d).data_2], 0x10); Darkvater@1688: DrawString(103, y + 9, STR_MESSAGES_ALL, 0); Darkvater@1688: DrawString(103, y + 9 + 12, STR_MESSAGE_SOUND, 0); truelight@0: truelight@0: } break; Darkvater@1688: Darkvater@1688: case WE_CLICK: Darkvater@1688: switch (e->click.widget) { Darkvater@1688: case 2: /* Clicked on any of the fake widgets */ Darkvater@1688: if (e->click.pt.x > 13 && e->click.pt.x < 89 && e->click.pt.y > 26 && e->click.pt.y < 146) { Darkvater@1688: int element = (e->click.pt.y - 26) / 12; Darkvater@1688: byte val = (GetNewsDisplayValue(element) + 1) % 3; Darkvater@1688: Darkvater@1688: SetMessageButtonStates(w, val, element); Darkvater@1688: SetNewsDisplayValue(element, val); Darkvater@1688: Darkvater@1688: WP(w, def_d).data_1 |= (1 << element); Darkvater@1688: w->flags4 |= 5 << WF_TIMEOUT_SHL; // XXX - setup unclick (fake widget) Darkvater@1688: SetWindowDirty(w); Darkvater@1688: } Darkvater@1688: break; Darkvater@1688: case 23: case 24: /* Dropdown menu for all settings */ Darkvater@1688: ShowDropDownMenu(w, message_opt, WP(w, def_d).data_2, 24, 0, 0); Darkvater@1688: break; Darkvater@1688: case 25: /* Change ticker sound on/off */ Darkvater@1688: _news_ticker_sound ^= 1; Darkvater@1688: TOGGLEBIT(w->click_state, e->click.widget); Darkvater@1688: InvalidateWidget(w, e->click.widget); Darkvater@1688: break; Darkvater@1688: default: { /* Clicked on the [<] .. [>] widgets */ Darkvater@1688: int wid = e->click.widget; Darkvater@1688: if (wid > 2 && wid < 23) { Darkvater@1688: int element = (wid - 3) / 2; Darkvater@1688: byte val = (GetNewsDisplayValue(element) + ((wid & 1) ? -1 : 1)) % 3; Darkvater@1688: Darkvater@1688: SetMessageButtonStates(w, val, element); Darkvater@1688: SetNewsDisplayValue(element, val); Darkvater@1688: SetWindowDirty(w); Darkvater@1688: } Darkvater@1688: } break; Darkvater@1688: } break; Darkvater@1688: Darkvater@1688: case WE_DROPDOWN_SELECT: /* Select all settings for newsmessages */ Darkvater@1688: WP(w, def_d).data_2 = e->dropdown.index; Darkvater@1688: _news_display_opt = message_val[WP(w, def_d).data_2]; Darkvater@1688: w->disabled_state = message_dis[WP(w, def_d).data_2]; Darkvater@1688: SetWindowDirty(w); Darkvater@1688: break; Darkvater@1688: Darkvater@1688: case WE_TIMEOUT: /* XXX - Hack to animate 'fake' buttons */ Darkvater@1688: WP(w, def_d).data_1 = 0; Darkvater@1688: SetWindowDirty(w); Darkvater@1688: break; Darkvater@1688: truelight@0: } truelight@0: } truelight@0: truelight@0: static const Widget _message_options_widgets[] = { Darkvater@1688: { WWT_CLOSEBOX, RESIZE_NONE, 13, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, Darkvater@1688: { WWT_CAPTION, RESIZE_NONE, 13, 11, 409, 0, 13, STR_0204_MESSAGE_OPTIONS, STR_018C_WINDOW_TITLE_DRAG_THIS}, Darkvater@1688: { WWT_PANEL, RESIZE_NONE, 13, 0, 409, 14, 184, STR_NULL, STR_NULL}, truelight@0: Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 4, 12, 26, 37, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 90, 98, 26, 37, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 4, 12, 38, 49, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 90, 98, 38, 49, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 4, 12, 50, 61, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 90, 98, 50, 61, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 4, 12, 62, 73, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 90, 98, 62, 73, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 4, 12, 74, 85, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 90, 98, 74, 85, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 4, 12, 86, 97, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 90, 98, 86, 97, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 4, 12, 98, 109, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 90, 98, 98, 109, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 4, 12, 110, 121, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 90, 98, 110, 121, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 4, 12, 122, 133, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 90, 98, 122, 133, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 4, 12, 134, 145, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: { WWT_PUSHIMGBTN, RESIZE_NONE, 3, 90, 98, 134, 145, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, Darkvater@1688: Darkvater@1688: { WWT_PANEL, RESIZE_NONE, 3, 4, 86, 154, 165, STR_NULL, STR_NULL}, Darkvater@1688: { WWT_CLOSEBOX, RESIZE_NONE, 3, 87, 98, 154, 165, STR_0225, STR_NULL}, Darkvater@1688: { WWT_4, RESIZE_NONE, 3, 4, 98, 166, 177, STR_02DB_OFF, STR_NULL}, truelight@0: darkvater@176: { WIDGETS_END}, truelight@0: }; truelight@0: truelight@0: static const WindowDesc _message_options_desc = { Darkvater@1688: 270, 22, 410, 185, tron@427: WC_GAME_OPTIONS, 0, truelight@0: WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, truelight@0: _message_options_widgets, truelight@0: MessageOptionsWndProc truelight@0: }; truelight@0: tron@427: void ShowMessageOptions(void) truelight@0: { truelight@0: DeleteWindowById(WC_GAME_OPTIONS, 0); truelight@0: AllocateWindowDesc(&_message_options_desc); truelight@0: }