tron@2186: /* $Id$ */ tron@2186: belugas@6605: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" peter1138@3155: #include "gui.h" rubidium@8603: #include "window_gui.h" rubidium@8720: #include "viewport_func.h" truelight@0: #include "news.h" rubidium@8766: #include "settings_type.h" belugas@8345: #include "transparency.h" rubidium@8610: #include "strings_func.h" rubidium@8627: #include "window_func.h" rubidium@8636: #include "date_func.h" rubidium@8640: #include "vehicle_base.h" rubidium@8653: #include "sound_func.h" rubidium@8710: #include "string_func.h" peter1138@8780: #include "widgets/dropdown_func.h" truelight@0: rubidium@8760: #include "table/sprites.h" rubidium@8760: #include "table/strings.h" rubidium@8760: rubidium@8094: /** @file news_gui.cpp rubidium@8094: * rubidium@4549: * News system is realized as a FIFO queue (in an array) rubidium@4549: * The positions in the queue can't be rearranged, we only access rubidium@4549: * the array elements through pointers to the elements. Once the rubidium@8094: * array is full, the oldest entry (\a _oldest_news) is being overwritten rubidium@8094: * by the newest (\a _latest_news). rubidium@4549: * rubidium@8094: * \verbatim rubidium@4549: * oldest current lastest rubidium@4549: * | | | rubidium@4549: * [O------------F-------------C---------L ] rubidium@4549: * | rubidium@4549: * forced rubidium@8094: * \endverbatim Darkvater@5254: * Darkvater@5254: * Of course by using an array we can have situations like Darkvater@5254: * rubidium@8094: * \verbatim Darkvater@5254: * [----L O-----F---------C-----------------] Darkvater@5254: * This is where we have wrapped around the array and have Darkvater@5254: * (MAX_NEWS - O) + L news items rubidium@8094: * \endverbatim rubidium@4549: */ dominik@79: rubidium@8094: /** Number of news items in the FIFO queue */ tron@427: #define MAX_NEWS 30 belugas@6605: #define NB_WIDG_PER_SETTING 4 tron@427: Darkvater@5246: typedef byte NewsID; tron@427: #define INVALID_NEWS 255 dominik@79: rubidium@8764: NewsItem _statusbar_news_item; rubidium@8766: uint32 _news_display_opt; rubidium@8766: bool _news_ticker_sound; rubidium@8094: static NewsItem _news_items[MAX_NEWS]; ///< The news FIFO queue rubidium@8094: static NewsID _current_news = INVALID_NEWS; ///< points to news item that should be shown next rubidium@8094: static NewsID _oldest_news = 0; ///< points to first item in fifo queue rubidium@8094: static NewsID _latest_news = INVALID_NEWS; ///< points to last item in fifo queue rubidium@8094: rubidium@8094: /** Forced news item. rubidium@8094: * Users can force an item by accessing the history or "last message". rubidium@8094: * If the message being shown was forced by the user, its index is stored in rubidium@8094: * _forced_news. Otherwise, \a _forced_news variable is INVALID_NEWS. */ Darkvater@5246: static NewsID _forced_news = INVALID_NEWS; dominik@79: rubidium@8094: static byte _total_news = 0; ///< Number of news items in FIFO queue @see _news_items truelight@0: rubidium@6535: void DrawNewsNewVehicleAvail(Window *w); truelight@0: void DrawNewsBankrupcy(Window *w); rubidium@6573: static void MoveToNextItem(); truelight@0: rubidium@6535: StringID GetNewsStringNewVehicleAvail(const NewsItem *ni); Darkvater@2101: StringID GetNewsStringBankrupcy(const NewsItem *ni); truelight@0: truelight@0: static DrawNewsCallbackProc * const _draw_news_callback[] = { rubidium@8094: DrawNewsNewVehicleAvail, ///< DNC_VEHICLEAVAIL rubidium@8094: DrawNewsBankrupcy, ///< DNC_BANKRUPCY truelight@0: }; truelight@0: rubidium@5838: extern GetNewsStringCallbackProc * const _get_news_string_callback[]; truelight@0: GetNewsStringCallbackProc * const _get_news_string_callback[] = { belugas@6674: GetNewsStringNewVehicleAvail, ///< DNC_VEHICLEAVAIL belugas@6674: GetNewsStringBankrupcy, ///< DNC_BANKRUPCY truelight@0: }; truelight@0: rubidium@8094: /** Initialize the news-items data structures */ rubidium@6573: void InitNewsItemStructs() 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: belugas@8320: DrawString(left + 2, top + 1, STR_00C6, TC_FROMSTRING); truelight@0: } truelight@0: truelight@0: static void NewsWindowProc(Window *w, WindowEvent *e) truelight@0: { tron@427: switch (e->event) { belugas@6674: 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: belugas@8320: DrawString(2, 1, STR_00C6, TC_FROMSTRING); dominik@79: tron@534: SetDParam(0, ni->date); belugas@8320: DrawStringRightAligned(428, 1, STR_01FF, TC_FROMSTRING); tron@427: tron@427: if (!(ni->flags & NF_VIEWPORT)) { rubidium@7762: CopyInDParam(0, ni->params, lengthof(ni->params)); tron@427: DrawStringMultiCenter(215, ni->display_mode == NM_NORMAL ? 76 : 56, Darkvater@6523: ni->string_id, w->width - 4); tron@427: } else { peter1138@6923: /* Back up transparency options to draw news view */ belugas@8345: TransparencyOptionBits to_backup = _transparency_opt; belugas@8345: _transparency_opt = 0; tron@427: DrawWindowViewport(w); belugas@8345: _transparency_opt = to_backup; 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, peter1138@5919: (ni->flags & NF_INCOLOR ? PALETTE_TO_TRANSPARENT : PALETTE_TO_STRUCT_GREY) | (1 << USE_COLORTABLE) tron@427: ); tron@427: rubidium@7762: CopyInDParam(0, ni->params, lengthof(ni->params)); Darkvater@6523: DrawStringMultiCenter(w->width / 2, 20, ni->string_id, w->width - 4); 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)) { rubidium@7762: CopyInDParam(0, ni->params, lengthof(ni->params)); tron@427: DrawStringMultiCenter(140, 38, ni->string_id, 276); tron@427: } else { tron@427: DrawWindowViewport(w); rubidium@7762: CopyInDParam(0, ni->params, lengthof(ni->params)); Darkvater@6523: DrawStringMultiCenter(w->width / 2, w->height - 16, ni->string_id, w->width - 4); tron@427: } tron@427: break; truelight@0: } truelight@0: } truelight@0: } break; truelight@0: truelight@0: case WE_CLICK: { belugas@4634: switch (e->we.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: belugas@4634: if (e->we.keypress.keycode == WKC_SPACE) { belugas@6674: /* Don't continue. */ belugas@4634: e->we.keypress.cont = false; truelight@0: DeleteWindow(w); truelight@0: } truelight@0: break; truelight@0: belugas@6674: case WE_MESSAGE: // The chatbar has notified us that is was either created or closed belugas@4634: switch (e->we.message.msg) { belugas@4634: case WE_CREATE: w->message.msg = e->we.message.wparam; break; darkvater@1648: case WE_DESTROY: w->message.msg = 0; break; darkvater@1648: } darkvater@1648: break; darkvater@1648: belugas@6674: 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: skidd13@8466: diff = Delta(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: rubidium@8094: /** rubidium@8094: * Return the correct index in the pseudo-fifo rubidium@8094: * queue and deals with overflows when increasing the index rubidium@8094: */ Darkvater@5246: static inline NewsID increaseIndex(NewsID i) dominik@79: { Darkvater@5258: assert(i != INVALID_NEWS); Darkvater@5245: return (i + 1) % MAX_NEWS; Darkvater@5245: } Darkvater@5245: rubidium@8094: /** rubidium@8094: * Return the correct index in the pseudo-fifo rubidium@8094: * queue and deals with overflows when decreasing the index rubidium@8094: */ Darkvater@5246: static inline NewsID decreaseIndex(NewsID i) Darkvater@5245: { Darkvater@5245: assert(i != INVALID_NEWS); Darkvater@5245: return (i + MAX_NEWS - 1) % MAX_NEWS; dominik@79: } dominik@79: rubidium@8094: /** rubidium@8094: * Add a new newsitem to be shown. rubidium@8094: * @param string String to display, can have special values based on parameter \a flags Darkvater@4873: * @param flags various control bits that will show various news-types. See macro NEWS_FLAGS() Darkvater@4873: * @param data_a news-specific value based on news type Darkvater@4873: * @param data_b news-specific value based on news type belugas@6977: * @note flags exists of 4 byte-sized extra parameters. rubidium@8094: * -# Bits 0 - 7 display_mode, any of the NewsMode enums (NM_) rubidium@8094: * -# Bits 8 - 15 news flags, any of the NewsFlags enums (NF_) rubidium@8094: * -# Bits 16 - 23 news category, any of the NewsType enums (NT_) rubidium@8094: * -# Bits 24 - 31 news callback function, any of the NewsCallback enums (DNC_) rubidium@8094: * rubidium@8094: * If the display mode is NM_CALLBACK, special news is shown and parameter rubidium@8094: * \a string has a special meaning. rubidium@8094: * - For DNC_TRAINAVAIL, DNC_ROADAVAIL, DNC_SHIPAVAIL, DNC_AIRCRAFTAVAIL messages: StringID is rubidium@8094: * the index of the engine that is shown rubidium@8094: * rubidium@8094: * - For DNC_BANKRUPCY: bytes 0-3 of StringID contains the player that is in trouble, rubidium@8094: * and 4-7 contains what kind of bankrupcy message is shown. rubidium@8094: * @see NewsBankrupcy rubidium@8094: * Darkvater@4873: * @see NewsMode Darkvater@4873: * @see NewsFlags Darkvater@4873: * @see NewsType rubidium@8094: * @see NewsCallback rubidium@8094: */ truelight@0: void AddNewsItem(StringID string, uint32 flags, uint data_a, uint data_b) truelight@0: { Darkvater@5246: NewsID l_news; truelight@0: tron@4000: if (_game_mode == GM_MENU) return; truelight@0: belugas@6674: /* check the rare case that the oldest (to be overwritten) news item is open */ Darkvater@5245: if (_total_news == MAX_NEWS && (_oldest_news == _current_news || _oldest_news == _forced_news)) Darkvater@5253: MoveToNextItem(); dominik@102: tron@427: if (_total_news < MAX_NEWS) _total_news++; truelight@193: Darkvater@5258: /* Increase _latest_news. If we have no news yet, use _oldest news as an Darkvater@5258: * index. We cannot use 0 as _oldest_news can jump around due to Darkvater@5258: * DeleteVehicleNews */ Darkvater@5244: l_news = _latest_news; Darkvater@5258: _latest_news = (_latest_news == INVALID_NEWS) ? _oldest_news : increaseIndex(_latest_news); dominik@79: Darkvater@5244: /* If the fifo-buffer is full, overwrite the oldest entry */ Darkvater@5245: if (l_news != INVALID_NEWS && _latest_news == _oldest_news) { Darkvater@5245: assert(_total_news == MAX_NEWS); Darkvater@5245: _oldest_news = increaseIndex(_oldest_news); Darkvater@5245: } dominik@79: Darkvater@5846: /*DEBUG(misc, 0, "+cur %3d, old %2d, lat %3d, for %3d, tot %2d", Darkvater@5846: _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/ Darkvater@5771: belugas@6674: /* Add news to _latest_news */ belugas@6674: { Darkvater@5245: Window *w; Darkvater@5245: NewsItem *ni = &_news_items[_latest_news]; Darkvater@5245: memset(ni, 0, sizeof(*ni)); dominik@79: Darkvater@5245: ni->string_id = string; Darkvater@5245: ni->display_mode = (byte)flags; Darkvater@5245: ni->flags = (byte)(flags >> 8); dominik@79: belugas@6674: /* show this news message in color? */ Darkvater@5245: if (_cur_year >= _patches.colored_news_year) ni->flags |= NF_INCOLOR; Darkvater@5245: Darkvater@5245: ni->type = (byte)(flags >> 16); Darkvater@5245: ni->callback = (byte)(flags >> 24); Darkvater@5245: ni->data_a = data_a; Darkvater@5245: ni->data_b = data_b; Darkvater@5245: ni->date = _date; rubidium@7762: CopyOutDParam(ni->params, 0, lengthof(ni->params)); Darkvater@5245: Darkvater@5245: w = FindWindowById(WC_MESSAGE_HISTORY, 0); Darkvater@5245: if (w == NULL) return; Darkvater@5245: SetWindowDirty(w); Darkvater@5245: w->vscroll.count = _total_news; Darkvater@5245: } truelight@0: } truelight@0: dominik@715: rubidium@8094: /** rubidium@8094: * Maximum age of news items. rubidium@8094: * Don't show item if it's older than x days, corresponds with NewsType in news.h rubidium@8094: * @see NewsType rubidium@8094: */ rubidium@8094: static const byte _news_items_age[NT_END] = { rubidium@8094: 60, ///< NT_ARRIVAL_PLAYER rubidium@8094: 60, ///< NT_ARRIVAL_OTHER rubidium@8094: 90, ///< NT_ACCIDENT rubidium@8094: 60, ///< NT_COMPANY_INFO rubidium@8094: 90, ///< NT_OPENCLOSE rubidium@8094: 30, ///< NT_ECONOMY rubidium@8387: 30, ///< NT_INDUSTRY_PLAYER rubidium@8387: 30, ///< NT_INDUSTRY_OTHER rubidium@8387: 30, ///< NT_INDUSTRY_NOBODY rubidium@8094: 150, ///< NT_ADVICE rubidium@8094: 30, ///< NT_NEW_VEHICLES rubidium@8094: 90, ///< NT_ACCEPTANCE rubidium@8094: 180, ///< NT_SUBSIDIES rubidium@8094: 60 ///< NT_GENERAL rubidium@8094: }; rubidium@8094: 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 = { rubidium@7837: WDP_CENTER, 476, 430, 170, 430, 170, rubidium@6144: WC_NEWS_WINDOW, WC_NONE, 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 = { rubidium@7837: WDP_CENTER, 476, 430, 130, 430, 130, rubidium@6144: WC_NEWS_WINDOW, WC_NONE, 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[] = { rubidium@4344: { WWT_PANEL, RESIZE_NONE, 5, 0, 279, 14, 86, 0x0, STR_NULL}, rubidium@4344: { WWT_CLOSEBOX, RESIZE_NONE, 5, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, rubidium@4344: { WWT_CAPTION, RESIZE_NONE, 5, 11, 279, 0, 13, STR_012C_MESSAGE, STR_NULL}, Darkvater@4939: { WWT_INSET, 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 = { rubidium@7837: WDP_CENTER, 476, 280, 87, 280, 87, rubidium@6144: WC_NEWS_WINDOW, WC_NONE, truelight@0: WDF_DEF_WIDGET, truelight@0: _news_type0_widgets, truelight@0: NewsWindowProc truelight@0: }; truelight@0: truelight@6609: static const SoundFx _news_sounds[NT_END] = { rubidium@8387: SND_1D_APPLAUSE, ///< NT_ARRIVAL_PLAYER rubidium@8387: SND_1D_APPLAUSE, ///< NT_ARRIVAL_OTHER rubidium@8387: SND_BEGIN, ///< NT_ACCIDENT rubidium@8387: SND_BEGIN, ///< NT_COMPANY_INFO rubidium@8387: SND_BEGIN, ///< NT_OPENCLOSE rubidium@8387: SND_BEGIN, ///< NT_ECONOMY rubidium@8387: SND_BEGIN, ///< NT_INDUSTRY_PLAYER rubidium@8387: SND_BEGIN, ///< NT_INDUSTRY_OTHER rubidium@8387: SND_BEGIN, ///< NT_INDUSTRY_NOBODY rubidium@8387: SND_BEGIN, ///< NT_ADVICE rubidium@8387: SND_1E_OOOOH, ///< NT_NEW_VEHICLES rubidium@8387: SND_BEGIN, ///< NT_ACCEPTANCE rubidium@8387: SND_BEGIN, ///< NT_SUBSIDIES rubidium@8387: SND_BEGIN, ///< NT_GENERAL truelight@6609: }; truelight@6609: truelight@6609: const char *_news_display_name[NT_END] = { truelight@6609: "arrival_player", truelight@6609: "arrival_other", truelight@6609: "accident", truelight@6609: "company_info", truelight@6609: "openclose", truelight@6609: "economy", rubidium@8387: "production_player", rubidium@8387: "production_other", rubidium@8387: "production_nobody", truelight@6609: "advice", truelight@6609: "new_vehicles", truelight@6609: "acceptance", truelight@6609: "subsidies", truelight@6609: "general", tron@427: }; truelight@0: rubidium@8094: /** rubidium@8094: * 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: { belugas@6605: assert(item < NT_END && GB(_news_display_opt, item * 2, 2) <= 2); tron@2549: return GB(_news_display_opt, item * 2, 2); Darkvater@1688: } Darkvater@1688: rubidium@8094: /** rubidium@8094: * 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: { belugas@6605: assert(item < NT_END && val <= 2); tron@2549: SB(_news_display_opt, item * 2, 2, val); Darkvater@1688: } Darkvater@1688: rubidium@8094: /** Open up an own newspaper window for the news item */ dominik@79: static void ShowNewspaper(NewsItem *ni) truelight@0: { truelight@0: Window *w; tron@2498: SoundFx sound; dominik@79: int top; Darkvater@4874: ni->flags &= ~NF_FORCE_BIG; dominik@79: ni->duration = 555; truelight@0: dominik@79: sound = _news_sounds[ni->type]; tron@2639: if (sound != 0) 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, truelight@7120: ni->data_a | (ni->flags & NF_VEHICLE ? 0x80000000 : 0), ZOOM_LVL_NEWS); 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, truelight@7120: ni->data_a | (ni->flags & NF_VEHICLE ? 0x80000000 : 0), ZOOM_LVL_NEWS); 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, truelight@7120: ni->data_a | (ni->flags & NF_VEHICLE ? 0x80000000 : 0), ZOOM_LVL_NEWS); tron@427: break; dominik@79: } truelight@0: } Darkvater@5771: Darkvater@5846: /*DEBUG(misc, 0, " cur %3d, old %2d, lat %3d, for %3d, tot %2d", Darkvater@5846: _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/ Darkvater@5771: 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: rubidium@8094: /** 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@2639: if (w != NULL) WP(w, def_d).data_1 = 360; dominik@79: } dominik@79: dominik@79: rubidium@8094: /** rubidium@8094: * Are we ready to show another news item? rubidium@8094: * Only if nothing is in the newsticker and no newspaper is displayed rubidium@8094: */ rubidium@6573: static bool ReadyForNextItem() dominik@79: { tron@427: const Window *w; Darkvater@5246: NewsID 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: belugas@6674: /* Ticker message belugas@6674: * Check if the status bar message is still being displayed? */ dominik@79: w = FindWindowById(WC_STATUS_BAR, 0); tron@2639: if (w != NULL && WP(w, const def_d).data_1 > -1280) return false; truelight@0: belugas@6674: /* Newspaper message, decrement duration counter */ Darkvater@5264: if (ni->duration != 0) ni->duration--; truelight@0: belugas@6674: /* neither newsticker nor newspaper are running */ Darkvater@5264: return (ni->duration == 0 || FindWindowById(WC_NEWS_WINDOW, 0) == NULL); dominik@79: } dominik@79: rubidium@8094: /** Move to the next news item */ rubidium@6573: static void MoveToNextItem() dominik@79: { dominik@79: DeleteWindowById(WC_NEWS_WINDOW, 0); tron@427: _forced_news = INVALID_NEWS; dominik@79: belugas@6674: /* if we're not at the last item, then move on */ tron@427: if (_current_news != _latest_news) { dominik@79: NewsItem *ni; dominik@79: Darkvater@5258: _current_news = (_current_news == INVALID_NEWS) ? _oldest_news : increaseIndex(_current_news); dominik@79: ni = &_news_items[_current_news]; dominik@79: belugas@6674: /* check the date, don't show too old items */ tron@2639: if (_date - _news_items_age[ni->type] > ni->date) return; dominik@79: tron@2639: switch (GetNewsDisplayValue(ni->type)) { rubidium@8094: default: NOT_REACHED(); rubidium@8094: case 0: { // Off - show nothing only a small reminder in the status bar rubidium@8094: Window *w = FindWindowById(WC_STATUS_BAR, 0); tron@2639: rubidium@8094: if (w != NULL) { rubidium@8094: WP(w, def_d).data_2 = 91; rubidium@8094: SetWindowDirty(w); rubidium@8094: } tron@2639: break; tron@2639: } tron@2639: rubidium@8094: case 1: // Summary - show ticker, but if forced big, cascade to full rubidium@8094: if (!(ni->flags & NF_FORCE_BIG)) { rubidium@8094: ShowTicker(ni); rubidium@8094: break; rubidium@8094: } rubidium@8094: /* Fallthrough */ rubidium@8094: rubidium@8094: case 2: // Full - show newspaper rubidium@8094: ShowNewspaper(ni); rubidium@8094: break; tron@2639: } truelight@0: } dominik@79: } truelight@0: rubidium@6573: void NewsLoop() truelight@0: { belugas@6674: /* no news item yet */ tron@427: if (_total_news == 0) return; dominik@79: Darkvater@5253: if (ReadyForNextItem()) MoveToNextItem(); dominik@79: } dominik@79: rubidium@8094: /** Do a forced show of a specific message */ Darkvater@5246: static void ShowNewsMessage(NewsID i) dominik@79: { tron@427: if (_total_news == 0) return; dominik@89: belugas@6674: /* Delete the news window */ dominik@79: DeleteWindowById(WC_NEWS_WINDOW, 0); dominik@79: belugas@6674: /* 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; Darkvater@4874: ni->flags |= NF_FORCE_BIG; dominik@79: DeleteWindowById(WC_NEWS_WINDOW, 0); dominik@79: ShowNewspaper(ni); dominik@79: } truelight@0: } truelight@0: rubidium@8094: /** Show previous news item */ rubidium@6573: void ShowLastNewsMessage() truelight@0: { Darkvater@5846: if (_forced_news == INVALID_NEWS) { Darkvater@5846: /* Not forced any news yet, show the current one, unless a news window is Darkvater@5846: * open (which can only be the current one), then show the previous item */ Darkvater@5846: const Window *w = FindWindowById(WC_NEWS_WINDOW, 0); Darkvater@6090: ShowNewsMessage((w == NULL || (_current_news == _oldest_news)) ? _current_news : decreaseIndex(_current_news)); Darkvater@5846: } else if (_forced_news == _oldest_news) { Darkvater@5846: /* We have reached the oldest news, start anew with the latest */ Darkvater@5846: ShowNewsMessage(_latest_news); Darkvater@5846: } else { Darkvater@5846: /* 'Scrolling' through news history show each one in turn */ Darkvater@5846: ShowNewsMessage(decreaseIndex(_forced_news)); dominik@83: } truelight@0: } truelight@0: dominik@79: dominik@89: /* return news by number, with 0 being the most rubidium@4549: * recent news. Returns INVALID_NEWS if end of queue reached. */ Darkvater@5246: static NewsID getNews(NewsID i) dominik@89: { tron@2639: if (i >= _total_news) return INVALID_NEWS; dominik@89: tron@2639: if (_latest_news < i) { dominik@89: i = _latest_news + MAX_NEWS - i; tron@2639: } else { dominik@89: i = _latest_news - i; tron@2639: } dominik@89: tron@427: i %= MAX_NEWS; dominik@89: return i; dominik@89: } dominik@89: rubidium@8094: /** rubidium@8094: * Draw an unformatted news message truncated to a maximum length. If Darkvater@2101: * length exceeds maximum length it will be postfixed by '...' Darkvater@2101: * @param x,y position of the string Darkvater@2101: * @param color the color the string will be shown in Darkvater@2101: * @param *ni NewsItem being printed Darkvater@2101: * @param maxw maximum width of string in pixels Darkvater@2101: */ Darkvater@2101: static void DrawNewsString(int x, int y, uint16 color, const NewsItem *ni, uint maxw) dominik@89: { Darkvater@2101: char buffer[512], buffer2[512]; peter1138@5108: const char *ptr; peter1138@5108: char *dest; dominik@89: StringID str; truelight@193: belugas@8350: if (ni->display_mode == NM_CALLBACK) { dominik@89: str = _get_news_string_callback[ni->callback](ni); dominik@89: } else { rubidium@7762: CopyInDParam(0, ni->params, lengthof(ni->params)); truelight@193: str = ni->string_id; dominik@89: } dominik@89: Darkvater@4912: GetString(buffer, str, lastof(buffer)); Darkvater@2101: /* Copy the just gotten string to another buffer to remove any formatting Darkvater@2101: * from it such as big fonts, etc. */ peter1138@5108: ptr = buffer; peter1138@5108: dest = buffer2; truelight@7038: WChar c_last = '\0'; peter1138@5108: for (;;) { peter1138@5108: WChar c = Utf8Consume(&ptr); peter1138@5108: if (c == 0) break; truelight@7038: /* Make a space from a newline, but ignore multiple newlines */ truelight@7038: if (c == '\n' && c_last != '\n') { truelight@7038: dest[0] = ' '; truelight@7038: dest++; truelight@7038: } else if (c == '\r') { Darkvater@2101: dest[0] = dest[1] = dest[2] = dest[3] = ' '; Darkvater@2101: dest += 4; peter1138@5108: } else if (IsPrintable(c)) { peter1138@5108: dest += Utf8Encode(dest, c); dominik@89: } truelight@7038: c_last = c; dominik@89: } Darkvater@2101: Darkvater@2101: *dest = '\0'; Darkvater@2101: /* Truncate and show string; postfixed by '...' if neccessary */ Darkvater@2101: DoDrawStringTruncated(buffer2, x, y, color, maxw); 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; Darkvater@5246: NewsID p, show; dominik@89: peter1138@3155: SetVScrollCount(w, _total_news); 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++) { belugas@6674: /* get news in correct order */ Darkvater@2101: const NewsItem *ni = &_news_items[getNews(p)]; dominik@89: tron@534: SetDParam(0, ni->date); belugas@8320: DrawString(4, y, STR_SHORT_DATE, TC_WHITE); dominik@89: belugas@8320: DrawNewsString(82, y, TC_WHITE, ni, w->width - 95); dominik@89: y += 12; dominik@89: } dominik@89: break; dominik@89: } dominik@89: dominik@89: case WE_CLICK: belugas@4634: switch (e->we.click.widget) { dominik@1097: case 3: { belugas@4634: int y = (e->we.click.pt.y - 19) / 12; Darkvater@5246: NewsID p = getNews(y + w->vscroll.pos); dominik@89: Darkvater@5246: if (p == INVALID_NEWS) break; dominik@89: Darkvater@5246: ShowNewsMessage(p); dominik@89: break; dominik@89: } dominik@89: } dominik@89: break; dominik@1097: dominik@1097: case WE_RESIZE: belugas@4634: w->vscroll.cap += e->we.sizing.diff.y / 12; dominik@1097: break; dominik@89: } dominik@89: } dominik@89: dominik@89: static const Widget _message_history_widgets[] = { rubidium@4344: { WWT_CLOSEBOX, RESIZE_NONE, 13, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, rubidium@4344: { WWT_CAPTION, RESIZE_RIGHT, 13, 11, 387, 0, 13, STR_MESSAGE_HISTORY, STR_018C_WINDOW_TITLE_DRAG_THIS}, rubidium@4344: { WWT_STICKYBOX, RESIZE_LR, 13, 388, 399, 0, 13, 0x0, STR_STICKY_BUTTON}, Darkvater@4938: { WWT_PANEL, RESIZE_RB, 13, 0, 387, 14, 139, 0x0, STR_MESSAGE_HISTORY_TIP}, rubidium@4344: { WWT_SCROLLBAR, RESIZE_LRB, 13, 388, 399, 14, 127, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, rubidium@4344: { 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 = { rubidium@7837: 240, 22, 400, 140, 400, 140, rubidium@6144: WC_MESSAGE_HISTORY, WC_NONE, 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: rubidium@8094: /** Display window with news messages history */ rubidium@6573: void ShowMessageHistory() 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: belugas@6605: rubidium@8387: /** News settings window widget offset constants */ belugas@6605: enum { peter1138@8839: WIDGET_NEWSOPT_DROP_SUMMARY = 4, ///< Dropdown that adjusts at once the level for all settings peter1138@8839: WIDGET_NEWSOPT_SOUNDTICKER = 6, ///< Button activating sound on events peter1138@8839: WIDGET_NEWSOPT_START_OPTION = 8, ///< First widget that is part of a group [<] .. [.] belugas@6605: }; belugas@6605: rubidium@8094: /** rubidium@8094: * Setup the disabled/enabled buttons in the message window Darkvater@1688: * If the value is 'off' disable the [<] widget, and enable the [>] one belugas@6605: * Same-wise for all the others. Starting value of 4 is the first widget Darkvater@1688: * group. These are grouped as [<][>] .. [<][>], etc. belugas@6605: * @param w Window been used belugas@6605: * @param value to set in the widget belugas@6605: * @param element index of the group of widget to set Darkvater@1688: */ Darkvater@1688: static void SetMessageButtonStates(Window *w, byte value, int element) Darkvater@1688: { belugas@6605: element *= NB_WIDG_PER_SETTING; belugas@4709: rubidium@8493: w->SetWidgetDisabledState(element + WIDGET_NEWSOPT_START_OPTION, value == 0); rubidium@8493: w->SetWidgetDisabledState(element + WIDGET_NEWSOPT_START_OPTION + 2, value == 2); Darkvater@1688: } truelight@0: belugas@6605: /** belugas@6605: * Event handler of the Message Options window belugas@6605: * @param w window pointer belugas@6605: * @param e event been triggered belugas@6605: */ 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: belugas@6605: /* WP(w, def_d).data_1 stores state of the ALL on/off/summary button */ tron@427: switch (e->event) { belugas@6605: case WE_CREATE: { belugas@6605: uint32 val = _news_display_opt; truelight@6628: uint32 all_val; belugas@6605: int i; truelight@0: belugas@6605: /* Set up the initial disabled buttons in the case of 'off' or 'full' */ truelight@6628: all_val = val & 0x3; truelight@6628: for (i = 0; i < NT_END; i++, val >>= 2) { truelight@6628: SetMessageButtonStates(w, val & 0x3, i); truelight@6628: /* If the value doesn't match the ALL-button value, set the ALL-button value to 'off' */ truelight@6628: if ((val & 0x3) != all_val) all_val = 0; truelight@6628: } truelight@6628: /* If all values are the same value, the ALL-button will take over this value */ truelight@6628: WP(w, def_d).data_1 = all_val; Darkvater@1688: } break; Darkvater@1688: belugas@6605: case WE_PAINT: { belugas@6605: uint32 val = _news_display_opt; belugas@6605: int i, y; belugas@4709: rubidium@8493: if (_news_ticker_sound) w->LowerWidget(WIDGET_NEWSOPT_SOUNDTICKER); peter1138@8839: peter1138@8839: w->widget[WIDGET_NEWSOPT_DROP_SUMMARY].data = message_opt[WP(w, def_d).data_1]; belugas@6605: DrawWindowWidgets(w); Darkvater@1688: belugas@6605: /* Draw the string of each setting on each button. */ belugas@6605: for (i = 0, y = 26; i < NT_END; i++, y += 12, val >>= 2) { belugas@6605: /* 51 comes from 13 + 89 (left and right of the button)+1, shiefted by one as to get division, belugas@6605: * which will give centered position */ belugas@8320: DrawStringCentered(51, y + 1, message_opt[val & 0x3], TC_BLACK); belugas@6605: } belugas@6605: } break; belugas@6605: belugas@6605: case WE_CLICK: belugas@6605: switch (e->we.click.widget) { belugas@6605: case WIDGET_NEWSOPT_DROP_SUMMARY: // Dropdown menu for all settings belugas@6605: ShowDropDownMenu(w, message_opt, WP(w, def_d).data_1, WIDGET_NEWSOPT_DROP_SUMMARY, 0, 0); belugas@6605: break; belugas@6605: belugas@6605: case WIDGET_NEWSOPT_SOUNDTICKER: // Change ticker sound on/off belugas@6605: _news_ticker_sound ^= 1; rubidium@8493: w->ToggleWidgetLoweredState(e->we.click.widget); glx@8524: w->InvalidateWidget(e->we.click.widget); belugas@6605: break; belugas@6605: belugas@6605: default: { // Clicked on the [<] .. [>] widgets belugas@6605: int wid = e->we.click.widget - WIDGET_NEWSOPT_START_OPTION; belugas@6605: if (wid >= 0 && wid < (NB_WIDG_PER_SETTING * NT_END)) { belugas@6605: int element = wid / NB_WIDG_PER_SETTING; belugas@6605: byte val = (GetNewsDisplayValue(element) + ((wid % NB_WIDG_PER_SETTING) ? 1 : -1)) % 3; belugas@6605: belugas@6605: SetMessageButtonStates(w, val, element); belugas@6605: SetNewsDisplayValue(element, val); belugas@6605: SetWindowDirty(w); belugas@6605: } belugas@6605: } break; belugas@6605: } break; belugas@6605: belugas@6605: case WE_DROPDOWN_SELECT: { // Select all settings for newsmessages belugas@6605: int i; belugas@6605: belugas@6605: WP(w, def_d).data_1 = e->we.dropdown.index; belugas@6605: belugas@6605: for (i = 0; i < NT_END; i++) { truelight@6609: SetMessageButtonStates(w, e->we.dropdown.index, i); belugas@6605: SetNewsDisplayValue(i, e->we.dropdown.index); belugas@6605: } belugas@6605: SetWindowDirty(w); belugas@6605: } break; truelight@0: } truelight@0: } truelight@0: rubidium@8387: rubidium@8387: /* rubidium@8387: * The news settings window widgets rubidium@8387: * rubidium@8387: * Main part of the window is a list of news-setting lines, one for each news category. rubidium@8387: * Each line is constructed by an expansion of the \c NEWS_SETTINGS_LINE macro rubidium@8387: */ rubidium@8387: rubidium@8387: /** rubidium@8387: * Macro to construct one news-setting line in the news-settings window. rubidium@8387: * One line consists of four widgets, namely rubidium@8387: * - A [<] button rubidium@8387: * - A [...] label rubidium@8387: * - A [>] button rubidium@8387: * - A text label describing the news category rubidium@8387: * Horizontal positions of the widgets are hard-coded, vertical start position is (\a basey + \a linenum * \c NEWS_SETTING_BASELINE_SKIP). rubidium@8387: * Height of one line is 12, with the text label shifted 1 pixel down. rubidium@8387: * rubidium@8387: * First line should be widget number WIDGET_NEWSOPT_START_OPTION rubidium@8387: * rubidium@8387: * @param basey: Base Y coordinate rubidium@8387: * @param linenum: Count, news-setting is the \a linenum-th line rubidium@8387: * @param text: StringID for the text label to display rubidium@8387: */ rubidium@8387: #define NEWS_SETTINGS_LINE(basey, linenum, text) \ rubidium@8387: { WWT_PUSHIMGBTN, RESIZE_NONE, COLOUR_YELLOW, \ rubidium@8387: 4, 12, basey + linenum * NEWS_SETTING_BASELINE_SKIP, basey + 11 + linenum * NEWS_SETTING_BASELINE_SKIP, \ rubidium@8387: SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, \ rubidium@8387: { WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_YELLOW, \ rubidium@8387: 13, 89, basey + linenum * NEWS_SETTING_BASELINE_SKIP, basey + 11 + linenum * NEWS_SETTING_BASELINE_SKIP, \ rubidium@8387: STR_EMPTY, STR_NULL}, \ rubidium@8387: { WWT_PUSHIMGBTN, RESIZE_NONE, COLOUR_YELLOW, \ rubidium@8387: 90, 98, basey + linenum * NEWS_SETTING_BASELINE_SKIP, basey + 11 + linenum * NEWS_SETTING_BASELINE_SKIP, \ rubidium@8387: SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, \ rubidium@8387: { WWT_TEXT, RESIZE_NONE, COLOUR_YELLOW, \ rubidium@8387: 103, 409, basey + 1 + linenum * NEWS_SETTING_BASELINE_SKIP, basey + 13 + linenum * NEWS_SETTING_BASELINE_SKIP, \ rubidium@8387: text, STR_NULL} rubidium@8387: rubidium@8387: static const int NEWS_SETTING_BASELINE_SKIP = 12; ///< Distance between two news-setting lines, should be at least 12 rubidium@8387: rubidium@8387: truelight@0: static const Widget _message_options_widgets[] = { rubidium@8387: { WWT_CLOSEBOX, RESIZE_NONE, COLOUR_BROWN, 0, 10, 0, 13, rubidium@8387: STR_00C5, STR_018B_CLOSE_WINDOW}, rubidium@8387: { WWT_CAPTION, RESIZE_NONE, COLOUR_BROWN, 11, 409, 0, 13, rubidium@8387: STR_0204_MESSAGE_OPTIONS, STR_018C_WINDOW_TITLE_DRAG_THIS}, rubidium@8387: { WWT_PANEL, RESIZE_NONE, COLOUR_BROWN, 0, 409, 14, 64 + NT_END * NEWS_SETTING_BASELINE_SKIP, rubidium@8387: 0x0, STR_NULL}, rubidium@4344: belugas@6605: /* Text at the top of the main panel, in black */ rubidium@8387: { WWT_LABEL, RESIZE_NONE, COLOUR_BROWN, rubidium@8387: 0, 409, 13, 26, rubidium@8387: STR_0205_MESSAGE_TYPES, STR_NULL}, belugas@6605: rubidium@8387: /* General drop down and sound button, widgets WIDGET_NEWSOPT_BTN_SUMMARY and WIDGET_NEWSOPT_DROP_SUMMARY */ peter1138@8839: { WWT_DROPDOWN, RESIZE_NONE, COLOUR_YELLOW, peter1138@8839: 4, 98, 34 + NT_END * NEWS_SETTING_BASELINE_SKIP, 45 + NT_END * NEWS_SETTING_BASELINE_SKIP, rubidium@8387: 0x0, STR_NULL}, belugas@6605: rubidium@8387: { WWT_TEXT, RESIZE_NONE, COLOUR_YELLOW, rubidium@8387: 103, 409, 35 + NT_END * NEWS_SETTING_BASELINE_SKIP, 47 + NT_END * NEWS_SETTING_BASELINE_SKIP, rubidium@8387: STR_MESSAGES_ALL, STR_NULL}, belugas@6605: rubidium@8387: /* Below is widget WIDGET_NEWSOPT_SOUNDTICKER */ rubidium@8387: { WWT_TEXTBTN_2, RESIZE_NONE, COLOUR_YELLOW, rubidium@8387: 4, 98, 46 + NT_END * NEWS_SETTING_BASELINE_SKIP, 57 + NT_END * NEWS_SETTING_BASELINE_SKIP, rubidium@8387: STR_02DB_OFF, STR_NULL}, truelight@6609: rubidium@8387: { WWT_TEXT, RESIZE_NONE, COLOUR_YELLOW, rubidium@8387: 103, 409, 47 + NT_END * NEWS_SETTING_BASELINE_SKIP, 59 + NT_END * NEWS_SETTING_BASELINE_SKIP, rubidium@8387: STR_MESSAGE_SOUND, STR_NULL}, rubidium@8387: rubidium@8387: /* List of news-setting lines (4 widgets for each line). rubidium@8387: * First widget must be number WIDGET_NEWSOPT_START_OPTION rubidium@8387: */ rubidium@8387: NEWS_SETTINGS_LINE(26, NT_ARRIVAL_PLAYER, STR_0206_ARRIVAL_OF_FIRST_VEHICLE), rubidium@8387: NEWS_SETTINGS_LINE(26, NT_ARRIVAL_OTHER, STR_0207_ARRIVAL_OF_FIRST_VEHICLE), rubidium@8387: NEWS_SETTINGS_LINE(26, NT_ACCIDENT, STR_0208_ACCIDENTS_DISASTERS), rubidium@8387: NEWS_SETTINGS_LINE(26, NT_COMPANY_INFO, STR_0209_COMPANY_INFORMATION), rubidium@8387: NEWS_SETTINGS_LINE(26, NT_OPENCLOSE, STR_NEWS_OPEN_CLOSE), rubidium@8387: NEWS_SETTINGS_LINE(26, NT_ECONOMY, STR_020A_ECONOMY_CHANGES), rubidium@8387: NEWS_SETTINGS_LINE(26, NT_INDUSTRY_PLAYER, STR_INDUSTRY_CHANGES_SERVED_BY_PLAYER), rubidium@8387: NEWS_SETTINGS_LINE(26, NT_INDUSTRY_OTHER, STR_INDUSTRY_CHANGES_SERVED_BY_OTHER), rubidium@8387: NEWS_SETTINGS_LINE(26, NT_INDUSTRY_NOBODY, STR_OTHER_INDUSTRY_PRODUCTION_CHANGES), rubidium@8387: NEWS_SETTINGS_LINE(26, NT_ADVICE, STR_020B_ADVICE_INFORMATION_ON_PLAYER), rubidium@8387: NEWS_SETTINGS_LINE(26, NT_NEW_VEHICLES, STR_020C_NEW_VEHICLES), rubidium@8387: NEWS_SETTINGS_LINE(26, NT_ACCEPTANCE, STR_020D_CHANGES_OF_CARGO_ACCEPTANCE), rubidium@8387: NEWS_SETTINGS_LINE(26, NT_SUBSIDIES, STR_020E_SUBSIDIES), rubidium@8387: NEWS_SETTINGS_LINE(26, NT_GENERAL, STR_020F_GENERAL_INFORMATION), belugas@6605: darkvater@176: { WIDGETS_END}, truelight@0: }; truelight@0: truelight@0: static const WindowDesc _message_options_desc = { rubidium@8387: 270, 22, 410, 65 + NT_END * NEWS_SETTING_BASELINE_SKIP, rubidium@8387: 410, 65 + NT_END * NEWS_SETTING_BASELINE_SKIP, rubidium@6144: WC_GAME_OPTIONS, WC_NONE, 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: rubidium@6573: void ShowMessageOptions() truelight@0: { truelight@0: DeleteWindowById(WC_GAME_OPTIONS, 0); truelight@0: AllocateWindowDesc(&_message_options_desc); truelight@0: } tron@3139: tron@3139: tron@3139: void DeleteVehicleNews(VehicleID vid, StringID news) tron@3139: { Darkvater@5246: NewsID n; tron@3139: Darkvater@5254: for (n = _oldest_news; _latest_news != INVALID_NEWS; n = increaseIndex(n)) { Darkvater@5245: const NewsItem *ni = &_news_items[n]; tron@3139: tron@3139: if (ni->flags & NF_VEHICLE && tron@3139: ni->data_a == vid && tron@3139: (news == INVALID_STRING_ID || ni->string_id == news)) { belugas@4171: Window *w; tron@3139: Darkvater@5846: /* If we delete a forced news and it is just before the current news Darkvater@5846: * then we need to advance to the next news (if any) */ Darkvater@5846: if (_forced_news == n) MoveToNextItem(); Darkvater@5846: if (_forced_news == INVALID_NEWS && _current_news == n) MoveToNextItem(); Darkvater@5254: _total_news--; tron@3139: Darkvater@5771: /* If this is the last news item, invalidate _latest_news */ Darkvater@5771: if (_total_news == 0) { Darkvater@5771: assert(_latest_news == _oldest_news); Darkvater@5254: _latest_news = INVALID_NEWS; rubidium@10327: _current_news = INVALID_NEWS; Darkvater@5254: } tron@3139: Darkvater@5254: /* Since we only imitate a FIFO removing an arbitrary element does need Darkvater@5254: * some magic. Remove the item by shifting head towards the tail. eg Darkvater@5254: * oldest remove last Darkvater@5254: * | | | Darkvater@5254: * [------O--------n-----L--] Darkvater@5254: * will become (change dramatized to make clear) Darkvater@5254: * [---------O-----------L--] Darkvater@5771: * We also need an update of the current, forced and visible (open window) Darkvater@5771: * news's as this shifting could change the items they were pointing to */ Darkvater@5254: if (_total_news != 0) { Darkvater@5771: w = FindWindowById(WC_NEWS_WINDOW, 0); Darkvater@5846: NewsID visible_news = (w != NULL) ? (NewsID)(WP(w, news_d).ni - _news_items) : INVALID_NEWS; Darkvater@5771: Darkvater@5846: for (NewsID i = n;; i = decreaseIndex(i)) { Darkvater@5246: _news_items[i] = _news_items[decreaseIndex(i)]; Darkvater@5254: Darkvater@6090: if (i != _latest_news) { Darkvater@6090: if (i == _current_news) _current_news = increaseIndex(_current_news); Darkvater@6090: if (i == _forced_news) _forced_news = increaseIndex(_forced_news); Darkvater@6090: if (i == visible_news) WP(w, news_d).ni = &_news_items[increaseIndex(visible_news)]; Darkvater@6090: } Darkvater@5771: Darkvater@5784: if (i == _oldest_news) break; Darkvater@5784: } Darkvater@5244: _oldest_news = increaseIndex(_oldest_news); tron@3139: } tron@3139: Darkvater@5846: /*DEBUG(misc, 0, "-cur %3d, old %2d, lat %3d, for %3d, tot %2d", Darkvater@5846: _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/ Darkvater@5771: tron@3139: w = FindWindowById(WC_MESSAGE_HISTORY, 0); tron@3143: if (w != NULL) { tron@3143: SetWindowDirty(w); tron@3143: w->vscroll.count = _total_news; tron@3143: } tron@3139: } Darkvater@5254: Darkvater@5254: if (n == _latest_news) break; tron@3139: } tron@3139: }