00001
00002
00003
00004
00005
00006
00007
00008
00009
00012 #include "stdafx.h"
00013 #include "gui.h"
00014 #include "viewport_func.h"
00015 #include "strings_func.h"
00016 #include "window_func.h"
00017 #include "date_func.h"
00018 #include "vehicle_base.h"
00019 #include "vehicle_func.h"
00020 #include "vehicle_gui.h"
00021 #include "station_base.h"
00022 #include "industry.h"
00023 #include "town.h"
00024 #include "sound_func.h"
00025 #include "string_func.h"
00026 #include "widgets/dropdown_func.h"
00027 #include "statusbar_gui.h"
00028 #include "company_manager_face.h"
00029 #include "company_func.h"
00030 #include "engine_base.h"
00031 #include "engine_gui.h"
00032 #include "core/geometry_func.hpp"
00033 #include "command_func.h"
00034 #include "company_base.h"
00035 #include "settings_internal.h"
00036
00037 #include "widgets/news_widget.h"
00038
00039 #include "table/strings.h"
00040
00041 const NewsItem *_statusbar_news_item = NULL;
00042
00043 static uint MIN_NEWS_AMOUNT = 30;
00044 static uint _total_news = 0;
00045 static NewsItem *_oldest_news = NULL;
00046 static NewsItem *_latest_news = NULL;
00047
00054 static const NewsItem *_forced_news = NULL;
00055
00057 static const NewsItem *_current_news = NULL;
00058
00059
00066 static TileIndex GetReferenceTile(NewsReferenceType reftype, uint32 ref)
00067 {
00068 switch (reftype) {
00069 case NR_TILE: return (TileIndex)ref;
00070 case NR_STATION: return Station::Get((StationID)ref)->xy;
00071 case NR_INDUSTRY: return Industry::Get((IndustryID)ref)->location.tile + TileDiffXY(1, 1);
00072 case NR_TOWN: return Town::Get((TownID)ref)->xy;
00073 default: return INVALID_TILE;
00074 }
00075 }
00076
00077
00078 static const NWidgetPart _nested_normal_news_widgets[] = {
00079 NWidget(WWT_PANEL, COLOUR_WHITE, WID_N_PANEL),
00080 NWidget(NWID_HORIZONTAL), SetPadding(1, 1, 0, 1),
00081 NWidget(WWT_TEXT, COLOUR_WHITE, WID_N_CLOSEBOX), SetDataTip(STR_SILVER_CROSS, STR_NULL), SetPadding(0, 0, 0, 1),
00082 NWidget(NWID_SPACER), SetFill(1, 0),
00083 NWidget(NWID_VERTICAL),
00084 NWidget(WWT_LABEL, COLOUR_WHITE, WID_N_DATE), SetDataTip(STR_DATE_LONG_SMALL, STR_NULL),
00085 NWidget(NWID_SPACER), SetFill(0, 1),
00086 EndContainer(),
00087 EndContainer(),
00088 NWidget(WWT_EMPTY, COLOUR_WHITE, WID_N_MESSAGE), SetMinimalSize(428, 154), SetPadding(0, 5, 1, 5),
00089 EndContainer(),
00090 };
00091
00092 static WindowDesc _normal_news_desc(
00093 WDP_MANUAL, NULL, 0, 0,
00094 WC_NEWS_WINDOW, WC_NONE,
00095 0,
00096 _nested_normal_news_widgets, lengthof(_nested_normal_news_widgets)
00097 );
00098
00099
00100 static const NWidgetPart _nested_vehicle_news_widgets[] = {
00101 NWidget(WWT_PANEL, COLOUR_WHITE, WID_N_PANEL),
00102 NWidget(NWID_HORIZONTAL), SetPadding(1, 1, 0, 1),
00103 NWidget(NWID_VERTICAL),
00104 NWidget(WWT_TEXT, COLOUR_WHITE, WID_N_CLOSEBOX), SetDataTip(STR_SILVER_CROSS, STR_NULL), SetPadding(0, 0, 0, 1),
00105 NWidget(NWID_SPACER), SetFill(0, 1),
00106 EndContainer(),
00107 NWidget(WWT_LABEL, COLOUR_WHITE, WID_N_VEH_TITLE), SetFill(1, 1), SetMinimalSize(419, 55), SetDataTip(STR_EMPTY, STR_NULL),
00108 EndContainer(),
00109 NWidget(WWT_PANEL, COLOUR_WHITE, WID_N_VEH_BKGND), SetPadding(0, 25, 1, 25),
00110 NWidget(NWID_VERTICAL),
00111 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_N_VEH_NAME), SetMinimalSize(369, 33), SetFill(1, 0),
00112 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_N_VEH_SPR), SetMinimalSize(369, 32), SetFill(1, 0),
00113 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_N_VEH_INFO), SetMinimalSize(369, 46), SetFill(1, 0),
00114 EndContainer(),
00115 EndContainer(),
00116 EndContainer(),
00117 };
00118
00119 static WindowDesc _vehicle_news_desc(
00120 WDP_MANUAL, NULL, 0, 0,
00121 WC_NEWS_WINDOW, WC_NONE,
00122 0,
00123 _nested_vehicle_news_widgets, lengthof(_nested_vehicle_news_widgets)
00124 );
00125
00126
00127 static const NWidgetPart _nested_company_news_widgets[] = {
00128 NWidget(WWT_PANEL, COLOUR_WHITE, WID_N_PANEL),
00129 NWidget(NWID_HORIZONTAL), SetPadding(1, 1, 0, 1),
00130 NWidget(NWID_VERTICAL),
00131 NWidget(WWT_TEXT, COLOUR_WHITE, WID_N_CLOSEBOX), SetDataTip(STR_SILVER_CROSS, STR_NULL), SetPadding(0, 0, 0, 1),
00132 NWidget(NWID_SPACER), SetFill(0, 1),
00133 EndContainer(),
00134 NWidget(WWT_LABEL, COLOUR_WHITE, WID_N_TITLE), SetFill(1, 1), SetMinimalSize(410, 20), SetDataTip(STR_EMPTY, STR_NULL),
00135 EndContainer(),
00136 NWidget(NWID_HORIZONTAL), SetPadding(0, 1, 1, 1),
00137 NWidget(NWID_VERTICAL),
00138 NWidget(WWT_EMPTY, COLOUR_WHITE, WID_N_MGR_FACE), SetMinimalSize(93, 119), SetPadding(2, 6, 2, 1),
00139 NWidget(NWID_HORIZONTAL),
00140 NWidget(WWT_EMPTY, COLOUR_WHITE, WID_N_MGR_NAME), SetMinimalSize(93, 24), SetPadding(0, 0, 0, 1),
00141 NWidget(NWID_SPACER), SetFill(1, 0),
00142 EndContainer(),
00143 NWidget(NWID_SPACER), SetFill(0, 1),
00144 EndContainer(),
00145 NWidget(WWT_EMPTY, COLOUR_WHITE, WID_N_COMPANY_MSG), SetFill(1, 1), SetMinimalSize(328, 150),
00146 EndContainer(),
00147 EndContainer(),
00148 };
00149
00150 static WindowDesc _company_news_desc(
00151 WDP_MANUAL, NULL, 0, 0,
00152 WC_NEWS_WINDOW, WC_NONE,
00153 0,
00154 _nested_company_news_widgets, lengthof(_nested_company_news_widgets)
00155 );
00156
00157
00158 static const NWidgetPart _nested_thin_news_widgets[] = {
00159 NWidget(WWT_PANEL, COLOUR_WHITE, WID_N_PANEL),
00160 NWidget(NWID_HORIZONTAL), SetPadding(1, 1, 0, 1),
00161 NWidget(WWT_TEXT, COLOUR_WHITE, WID_N_CLOSEBOX), SetDataTip(STR_SILVER_CROSS, STR_NULL), SetPadding(0, 0, 0, 1),
00162 NWidget(NWID_SPACER), SetFill(1, 0),
00163 NWidget(NWID_VERTICAL),
00164 NWidget(WWT_LABEL, COLOUR_WHITE, WID_N_DATE), SetDataTip(STR_DATE_LONG_SMALL, STR_NULL),
00165 NWidget(NWID_SPACER), SetFill(0, 1),
00166 EndContainer(),
00167 EndContainer(),
00168 NWidget(WWT_EMPTY, COLOUR_WHITE, WID_N_MESSAGE), SetMinimalSize(428, 48), SetFill(1, 0), SetPadding(0, 5, 0, 5),
00169 NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_N_VIEWPORT), SetMinimalSize(426, 70), SetPadding(1, 2, 2, 2),
00170 EndContainer(),
00171 };
00172
00173 static WindowDesc _thin_news_desc(
00174 WDP_MANUAL, NULL, 0, 0,
00175 WC_NEWS_WINDOW, WC_NONE,
00176 0,
00177 _nested_thin_news_widgets, lengthof(_nested_thin_news_widgets)
00178 );
00179
00180
00181 static const NWidgetPart _nested_small_news_widgets[] = {
00182
00183 NWidget(NWID_HORIZONTAL),
00184 NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE, WID_N_CLOSEBOX),
00185 NWidget(WWT_EMPTY, COLOUR_LIGHT_BLUE, WID_N_CAPTION), SetFill(1, 0),
00186 EndContainer(),
00187
00188
00189 NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_N_HEADLINE),
00190 NWidget(WWT_INSET, COLOUR_LIGHT_BLUE, WID_N_INSET), SetPadding(2, 2, 2, 2),
00191 NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_N_VIEWPORT), SetPadding(1, 1, 1, 1), SetMinimalSize(274, 47), SetFill(1, 0),
00192 EndContainer(),
00193 NWidget(WWT_EMPTY, COLOUR_WHITE, WID_N_MESSAGE), SetMinimalSize(275, 20), SetFill(1, 0), SetPadding(0, 5, 0, 5),
00194 EndContainer(),
00195 };
00196
00197 static WindowDesc _small_news_desc(
00198 WDP_MANUAL, NULL, 0, 0,
00199 WC_NEWS_WINDOW, WC_NONE,
00200 0,
00201 _nested_small_news_widgets, lengthof(_nested_small_news_widgets)
00202 );
00203
00207 static WindowDesc* _news_window_layout[] = {
00208 &_thin_news_desc,
00209 &_small_news_desc,
00210 &_normal_news_desc,
00211 &_vehicle_news_desc,
00212 &_company_news_desc,
00213 };
00214
00215 WindowDesc* GetNewsWindowLayout(NewsFlag flags)
00216 {
00217 uint layout = GB(flags, NFB_WINDOW_LAYOUT, NFB_WINDOW_LAYOUT_COUNT);
00218 assert(layout < lengthof(_news_window_layout));
00219 return _news_window_layout[layout];
00220 }
00221
00225 static NewsTypeData _news_type_data[] = {
00226
00227 NewsTypeData("news_display.arrival_player", 60, SND_1D_APPLAUSE ),
00228 NewsTypeData("news_display.arrival_other", 60, SND_1D_APPLAUSE ),
00229 NewsTypeData("news_display.accident", 90, SND_BEGIN ),
00230 NewsTypeData("news_display.company_info", 60, SND_BEGIN ),
00231 NewsTypeData("news_display.open", 90, SND_BEGIN ),
00232 NewsTypeData("news_display.close", 90, SND_BEGIN ),
00233 NewsTypeData("news_display.economy", 30, SND_BEGIN ),
00234 NewsTypeData("news_display.production_player", 30, SND_BEGIN ),
00235 NewsTypeData("news_display.production_other", 30, SND_BEGIN ),
00236 NewsTypeData("news_display.production_nobody", 30, SND_BEGIN ),
00237 NewsTypeData("news_display.advice", 150, SND_BEGIN ),
00238 NewsTypeData("news_display.new_vehicles", 30, SND_1E_OOOOH ),
00239 NewsTypeData("news_display.acceptance", 90, SND_BEGIN ),
00240 NewsTypeData("news_display.subsidies", 180, SND_BEGIN ),
00241 NewsTypeData("news_display.general", 60, SND_BEGIN ),
00242 };
00243
00244 assert_compile(lengthof(_news_type_data) == NT_END);
00245
00250 NewsDisplay NewsTypeData::GetDisplay() const
00251 {
00252 uint index;
00253 const SettingDesc *sd = GetSettingFromName(this->name, &index);
00254 assert(sd != NULL);
00255 void *ptr = GetVariableAddress(NULL, &sd->save);
00256 return (NewsDisplay)ReadValue(ptr, sd->save.conv);
00257 }
00258
00260 struct NewsWindow : Window {
00261 uint16 chat_height;
00262 uint16 status_height;
00263 const NewsItem *ni;
00264 static uint duration;
00265
00266 NewsWindow(WindowDesc *desc, const NewsItem *ni) : Window(desc), ni(ni)
00267 {
00268 NewsWindow::duration = 555;
00269 const Window *w = FindWindowByClass(WC_SEND_NETWORK_MSG);
00270 this->chat_height = (w != NULL) ? w->height : 0;
00271 this->status_height = FindWindowById(WC_STATUS_BAR, 0)->height;
00272
00273 this->flags |= WF_DISABLE_VP_SCROLL;
00274
00275 this->CreateNestedTree();
00276
00277
00278 if (desc == &_company_news_desc) this->GetWidget<NWidgetCore>(WID_N_TITLE)->widget_data = this->ni->params[0];
00279
00280 this->FinishInitNested(0);
00281
00282
00283 NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_N_VIEWPORT);
00284 if (nvp != NULL) {
00285 nvp->InitializeViewport(this, ni->reftype1 == NR_VEHICLE ? 0x80000000 | ni->ref1 : GetReferenceTile(ni->reftype1, ni->ref1), ZOOM_LVL_NEWS);
00286 if (this->ni->flags & NF_NO_TRANSPARENT) nvp->disp_flags |= ND_NO_TRANSPARENCY;
00287 if ((this->ni->flags & NF_INCOLOUR) == 0) {
00288 nvp->disp_flags |= ND_SHADE_GREY;
00289 } else if (this->ni->flags & NF_SHADE) {
00290 nvp->disp_flags |= ND_SHADE_DIMMED;
00291 }
00292 }
00293
00294 PositionNewsMessage(this);
00295 }
00296
00297 void DrawNewsBorder(const Rect &r) const
00298 {
00299 GfxFillRect(r.left, r.top, r.right, r.bottom, PC_WHITE);
00300
00301 GfxFillRect(r.left, r.top, r.left, r.bottom, PC_BLACK);
00302 GfxFillRect(r.right, r.top, r.right, r.bottom, PC_BLACK);
00303 GfxFillRect(r.left, r.top, r.right, r.top, PC_BLACK);
00304 GfxFillRect(r.left, r.bottom, r.right, r.bottom, PC_BLACK);
00305 }
00306
00307 virtual Point OnInitialPosition(int16 sm_width, int16 sm_height, int window_number)
00308 {
00309 Point pt = { 0, _screen.height };
00310 return pt;
00311 }
00312
00313 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
00314 {
00315 StringID str = STR_NULL;
00316 switch (widget) {
00317 case WID_N_MESSAGE:
00318 CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
00319 str = this->ni->string_id;
00320 break;
00321
00322 case WID_N_COMPANY_MSG:
00323 str = this->GetCompanyMessageString();
00324 break;
00325
00326 case WID_N_VEH_NAME:
00327 case WID_N_VEH_TITLE:
00328 str = this->GetNewVehicleMessageString(widget);
00329 break;
00330
00331 case WID_N_VEH_INFO: {
00332 assert(this->ni->reftype1 == NR_ENGINE);
00333 EngineID engine = this->ni->ref1;
00334 str = GetEngineInfoString(engine);
00335 break;
00336 }
00337 default:
00338 return;
00339 }
00340
00341
00342 Dimension d = *size;
00343 d.width = (d.width >= padding.width) ? d.width - padding.width : 0;
00344 d.height = (d.height >= padding.height) ? d.height - padding.height : 0;
00345 d = GetStringMultiLineBoundingBox(str, d);
00346 d.width += padding.width;
00347 d.height += padding.height;
00348 *size = maxdim(*size, d);
00349 }
00350
00351 virtual void SetStringParameters(int widget) const
00352 {
00353 if (widget == WID_N_DATE) SetDParam(0, this->ni->date);
00354 }
00355
00356 virtual void DrawWidget(const Rect &r, int widget) const
00357 {
00358 switch (widget) {
00359 case WID_N_CAPTION:
00360 DrawCaption(r, COLOUR_LIGHT_BLUE, this->owner, STR_NEWS_MESSAGE_CAPTION);
00361 break;
00362
00363 case WID_N_PANEL:
00364 this->DrawNewsBorder(r);
00365 break;
00366
00367 case WID_N_MESSAGE:
00368 CopyInDParam(0, this->ni->params, lengthof(this->ni->params));
00369 DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->ni->string_id, TC_FROMSTRING, SA_CENTER);
00370 break;
00371
00372 case WID_N_MGR_FACE: {
00373 const CompanyNewsInformation *cni = (const CompanyNewsInformation*)this->ni->free_data;
00374 DrawCompanyManagerFace(cni->face, cni->colour, r.left, r.top);
00375 GfxFillRect(r.left + 1, r.top, r.left + 1 + 91, r.top + 118, PALETTE_NEWSPAPER, FILLRECT_RECOLOUR);
00376 break;
00377 }
00378 case WID_N_MGR_NAME: {
00379 const CompanyNewsInformation *cni = (const CompanyNewsInformation*)this->ni->free_data;
00380 SetDParamStr(0, cni->president_name);
00381 DrawStringMultiLine(r.left, r.right, r.top, r.bottom, STR_JUST_RAW_STRING, TC_FROMSTRING, SA_CENTER);
00382 break;
00383 }
00384 case WID_N_COMPANY_MSG:
00385 DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->GetCompanyMessageString(), TC_FROMSTRING, SA_CENTER);
00386 break;
00387
00388 case WID_N_VEH_BKGND:
00389 GfxFillRect(r.left, r.top, r.right, r.bottom, PC_GREY);
00390 break;
00391
00392 case WID_N_VEH_NAME:
00393 case WID_N_VEH_TITLE:
00394 DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->GetNewVehicleMessageString(widget), TC_FROMSTRING, SA_CENTER);
00395 break;
00396
00397 case WID_N_VEH_SPR: {
00398 assert(this->ni->reftype1 == NR_ENGINE);
00399 EngineID engine = this->ni->ref1;
00400 DrawVehicleEngine(r.left, r.right, (r.left + r.right) / 2, (r.top + r.bottom) / 2, engine, GetEnginePalette(engine, _local_company), EIT_PREVIEW);
00401 GfxFillRect(r.left, r.top, r.right, r.bottom, PALETTE_NEWSPAPER, FILLRECT_RECOLOUR);
00402 break;
00403 }
00404 case WID_N_VEH_INFO: {
00405 assert(this->ni->reftype1 == NR_ENGINE);
00406 EngineID engine = this->ni->ref1;
00407 DrawStringMultiLine(r.left, r.right, r.top, r.bottom, GetEngineInfoString(engine), TC_FROMSTRING, SA_CENTER);
00408 break;
00409 }
00410 }
00411 }
00412
00413 virtual void OnClick(Point pt, int widget, int click_count)
00414 {
00415 switch (widget) {
00416 case WID_N_CLOSEBOX:
00417 NewsWindow::duration = 0;
00418 delete this;
00419 _forced_news = NULL;
00420 break;
00421
00422 case WID_N_CAPTION:
00423 if (this->ni->reftype1 == NR_VEHICLE) {
00424 const Vehicle *v = Vehicle::Get(this->ni->ref1);
00425 ShowVehicleViewWindow(v);
00426 }
00427 break;
00428
00429 case WID_N_VIEWPORT:
00430 break;
00431
00432 default:
00433 if (this->ni->reftype1 == NR_VEHICLE) {
00434 const Vehicle *v = Vehicle::Get(this->ni->ref1);
00435 ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos);
00436 } else {
00437 TileIndex tile1 = GetReferenceTile(this->ni->reftype1, this->ni->ref1);
00438 TileIndex tile2 = GetReferenceTile(this->ni->reftype2, this->ni->ref2);
00439 if (_ctrl_pressed) {
00440 if (tile1 != INVALID_TILE) ShowExtraViewPortWindow(tile1);
00441 if (tile2 != INVALID_TILE) ShowExtraViewPortWindow(tile2);
00442 } else {
00443 if ((tile1 == INVALID_TILE || !ScrollMainWindowToTile(tile1)) && tile2 != INVALID_TILE) {
00444 ScrollMainWindowToTile(tile2);
00445 }
00446 }
00447 }
00448 break;
00449 }
00450 }
00451
00452 virtual EventState OnKeyPress(uint16 key, uint16 keycode)
00453 {
00454 if (keycode == WKC_SPACE) {
00455
00456 delete this;
00457 return ES_HANDLED;
00458 }
00459 return ES_NOT_HANDLED;
00460 }
00461
00467 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
00468 {
00469 if (!gui_scope) return;
00470
00471 int newtop = this->top + this->chat_height - data;
00472 this->chat_height = data;
00473 this->SetWindowTop(newtop);
00474 }
00475
00476 virtual void OnTick()
00477 {
00478
00479 int newtop = max(this->top - 4, _screen.height - this->height - this->status_height - this->chat_height);
00480 this->SetWindowTop(newtop);
00481 }
00482
00483 private:
00488 void SetWindowTop(int newtop)
00489 {
00490 if (this->top == newtop) return;
00491
00492 int mintop = min(newtop, this->top);
00493 int maxtop = max(newtop, this->top);
00494 if (this->viewport != NULL) this->viewport->top += newtop - this->top;
00495 this->top = newtop;
00496
00497 SetDirtyBlocks(this->left, mintop, this->left + this->width, maxtop + this->height);
00498 }
00499
00500 StringID GetCompanyMessageString() const
00501 {
00502
00503 CopyInDParam(0, this->ni->params + 2, lengthof(this->ni->params) - 2);
00504 return this->ni->params[1];
00505 }
00506
00507 StringID GetNewVehicleMessageString(int widget) const
00508 {
00509 assert(this->ni->reftype1 == NR_ENGINE);
00510 EngineID engine = this->ni->ref1;
00511
00512 switch (widget) {
00513 case WID_N_VEH_TITLE:
00514 SetDParam(0, GetEngineCategoryName(engine));
00515 return STR_NEWS_NEW_VEHICLE_NOW_AVAILABLE;
00516
00517 case WID_N_VEH_NAME:
00518 SetDParam(0, engine);
00519 return STR_NEWS_NEW_VEHICLE_TYPE;
00520
00521 default:
00522 NOT_REACHED();
00523 }
00524 }
00525 };
00526
00527 uint NewsWindow::duration = 0;
00528
00529
00531 static void ShowNewspaper(const NewsItem *ni)
00532 {
00533 SoundFx sound = _news_type_data[ni->type].sound;
00534 if (sound != 0 && _settings_client.sound.news_full) SndPlayFx(sound);
00535
00536 new NewsWindow(GetNewsWindowLayout(ni->flags), ni);
00537 }
00538
00540 static void ShowTicker(const NewsItem *ni)
00541 {
00542 if (_settings_client.sound.news_ticker) SndPlayFx(SND_16_MORSE);
00543
00544 _statusbar_news_item = ni;
00545 InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_TICKER);
00546 }
00547
00549 void InitNewsItemStructs()
00550 {
00551 for (NewsItem *ni = _oldest_news; ni != NULL; ) {
00552 NewsItem *next = ni->next;
00553 delete ni;
00554 ni = next;
00555 }
00556
00557 _total_news = 0;
00558 _oldest_news = NULL;
00559 _latest_news = NULL;
00560 _forced_news = NULL;
00561 _current_news = NULL;
00562 _statusbar_news_item = NULL;
00563 NewsWindow::duration = 0;
00564 }
00565
00570 static bool ReadyForNextItem()
00571 {
00572 const NewsItem *ni = _forced_news == NULL ? _current_news : _forced_news;
00573 if (ni == NULL) return true;
00574
00575
00576
00577 if (IsNewsTickerShown()) return false;
00578
00579
00580 if (NewsWindow::duration != 0) NewsWindow::duration--;
00581
00582
00583 return (NewsWindow::duration == 0 || FindWindowById(WC_NEWS_WINDOW, 0) == NULL);
00584 }
00585
00587 static void MoveToNextItem()
00588 {
00589 InvalidateWindowData(WC_STATUS_BAR, 0, SBI_NEWS_DELETED);
00590 DeleteWindowById(WC_NEWS_WINDOW, 0);
00591 _forced_news = NULL;
00592 _statusbar_news_item = NULL;
00593
00594
00595 if (_current_news != _latest_news) {
00596 _current_news = (_current_news == NULL) ? _oldest_news : _current_news->next;
00597 const NewsItem *ni = _current_news;
00598 const NewsType type = ni->type;
00599
00600
00601 if (_date - _news_type_data[type].age > ni->date) return;
00602
00603 switch (_news_type_data[type].GetDisplay()) {
00604 default: NOT_REACHED();
00605 case ND_OFF:
00606 InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_REMINDER);
00607 break;
00608
00609 case ND_SUMMARY:
00610 ShowTicker(ni);
00611 break;
00612
00613 case ND_FULL:
00614 ShowNewspaper(ni);
00615 break;
00616 }
00617 }
00618 }
00619
00633 void AddNewsItem(StringID string, NewsType type, NewsFlag flags, NewsReferenceType reftype1, uint32 ref1, NewsReferenceType reftype2, uint32 ref2, void *free_data)
00634 {
00635 if (_game_mode == GM_MENU) return;
00636
00637
00638 NewsItem *ni = new NewsItem;
00639
00640 ni->string_id = string;
00641 ni->type = type;
00642 ni->flags = flags;
00643
00644
00645 if (_cur_year >= _settings_client.gui.coloured_news_year) ni->flags |= NF_INCOLOUR;
00646
00647 ni->reftype1 = reftype1;
00648 ni->reftype2 = reftype2;
00649 ni->ref1 = ref1;
00650 ni->ref2 = ref2;
00651 ni->free_data = free_data;
00652 ni->date = _date;
00653 CopyOutDParam(ni->params, 0, lengthof(ni->params));
00654
00655 if (_total_news++ == 0) {
00656 assert(_oldest_news == NULL);
00657 _oldest_news = ni;
00658 ni->prev = NULL;
00659 } else {
00660 assert(_latest_news->next == NULL);
00661 _latest_news->next = ni;
00662 ni->prev = _latest_news;
00663 }
00664
00665 ni->next = NULL;
00666 _latest_news = ni;
00667
00668 SetWindowDirty(WC_MESSAGE_HISTORY, 0);
00669 }
00670
00683 CommandCost CmdCustomNewsItem(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
00684 {
00685 if (_current_company != OWNER_DEITY) return CMD_ERROR;
00686
00687 NewsType type = (NewsType)GB(p1, 0, 8);
00688 NewsReferenceType reftype1 = (NewsReferenceType)GB(p1, 8, 8);
00689 CompanyID company = (CompanyID)GB(p1, 16, 8);
00690
00691 if (company != INVALID_OWNER && !Company::IsValidID(company)) return CMD_ERROR;
00692 if (type >= NT_END) return CMD_ERROR;
00693 if (StrEmpty(text)) return CMD_ERROR;
00694
00695 switch (reftype1) {
00696 case NR_NONE: break;
00697 case NR_TILE:
00698 if (!IsValidTile(p2)) return CMD_ERROR;
00699 break;
00700
00701 case NR_VEHICLE:
00702 if (!Vehicle::IsValidID(p2)) return CMD_ERROR;
00703 break;
00704
00705 case NR_STATION:
00706 if (!Station::IsValidID(p2)) return CMD_ERROR;
00707 break;
00708
00709 case NR_INDUSTRY:
00710 if (!Industry::IsValidID(p2)) return CMD_ERROR;
00711 break;
00712
00713 case NR_TOWN:
00714 if (!Town::IsValidID(p2)) return CMD_ERROR;
00715 break;
00716
00717 case NR_ENGINE:
00718 if (!Engine::IsValidID(p2)) return CMD_ERROR;
00719 break;
00720
00721 default: return CMD_ERROR;
00722 }
00723
00724 if (company != INVALID_OWNER && company != _local_company) return CommandCost();
00725
00726 if (flags & DC_EXEC) {
00727 char *news = strdup(text);
00728 SetDParamStr(0, news);
00729 AddNewsItem(STR_NEWS_CUSTOM_ITEM, type, NF_NORMAL, reftype1, p2, NR_NONE, UINT32_MAX, news);
00730 }
00731
00732 return CommandCost();
00733 }
00734
00736 static void DeleteNewsItem(NewsItem *ni)
00737 {
00738
00739 if (ni->prev != NULL) {
00740 ni->prev->next = ni->next;
00741 } else {
00742 assert(_oldest_news == ni);
00743 _oldest_news = ni->next;
00744 }
00745
00746 if (ni->next != NULL) {
00747 ni->next->prev = ni->prev;
00748 } else {
00749 assert(_latest_news == ni);
00750 _latest_news = ni->prev;
00751 }
00752
00753 _total_news--;
00754
00755 if (_forced_news == ni || _current_news == ni || _statusbar_news_item == ni) {
00756
00757
00758 if (_current_news == ni) _current_news = ni->prev;
00759
00760
00761
00762 MoveToNextItem();
00763 }
00764
00765 delete ni;
00766
00767 SetWindowDirty(WC_MESSAGE_HISTORY, 0);
00768 }
00769
00776 void DeleteVehicleNews(VehicleID vid, StringID news)
00777 {
00778 NewsItem *ni = _oldest_news;
00779
00780 while (ni != NULL) {
00781 NewsItem *next = ni->next;
00782 if (((ni->reftype1 == NR_VEHICLE && ni->ref1 == vid) || (ni->reftype2 == NR_VEHICLE && ni->ref2 == vid)) &&
00783 (news == INVALID_STRING_ID || ni->string_id == news)) {
00784 DeleteNewsItem(ni);
00785 }
00786 ni = next;
00787 }
00788 }
00789
00795 void DeleteStationNews(StationID sid)
00796 {
00797 NewsItem *ni = _oldest_news;
00798
00799 while (ni != NULL) {
00800 NewsItem *next = ni->next;
00801 if ((ni->reftype1 == NR_STATION && ni->ref1 == sid) || (ni->reftype2 == NR_STATION && ni->ref2 == sid)) {
00802 DeleteNewsItem(ni);
00803 }
00804 ni = next;
00805 }
00806 }
00807
00812 void DeleteIndustryNews(IndustryID iid)
00813 {
00814 NewsItem *ni = _oldest_news;
00815
00816 while (ni != NULL) {
00817 NewsItem *next = ni->next;
00818 if ((ni->reftype1 == NR_INDUSTRY && ni->ref1 == iid) || (ni->reftype2 == NR_INDUSTRY && ni->ref2 == iid)) {
00819 DeleteNewsItem(ni);
00820 }
00821 ni = next;
00822 }
00823 }
00824
00828 void DeleteInvalidEngineNews()
00829 {
00830 NewsItem *ni = _oldest_news;
00831
00832 while (ni != NULL) {
00833 NewsItem *next = ni->next;
00834 if ((ni->reftype1 == NR_ENGINE && (!Engine::IsValidID(ni->ref1) || !Engine::Get(ni->ref1)->IsEnabled())) ||
00835 (ni->reftype2 == NR_ENGINE && (!Engine::IsValidID(ni->ref2) || !Engine::Get(ni->ref2)->IsEnabled()))) {
00836 DeleteNewsItem(ni);
00837 }
00838 ni = next;
00839 }
00840 }
00841
00842 static void RemoveOldNewsItems()
00843 {
00844 NewsItem *next;
00845 for (NewsItem *cur = _oldest_news; _total_news > MIN_NEWS_AMOUNT && cur != NULL; cur = next) {
00846 next = cur->next;
00847 if (_date - _news_type_data[cur->type].age * _settings_client.gui.news_message_timeout > cur->date) DeleteNewsItem(cur);
00848 }
00849 }
00850
00857 void ChangeVehicleNews(VehicleID from_index, VehicleID to_index)
00858 {
00859 for (NewsItem *ni = _oldest_news; ni != NULL; ni = ni->next) {
00860 if (ni->reftype1 == NR_VEHICLE && ni->ref1 == from_index) ni->ref1 = to_index;
00861 if (ni->reftype2 == NR_VEHICLE && ni->ref2 == from_index) ni->ref2 = to_index;
00862 if (ni->flags & NF_VEHICLE_PARAM0 && ni->params[0] == from_index) ni->params[0] = to_index;
00863 }
00864 }
00865
00866 void NewsLoop()
00867 {
00868
00869 if (_total_news == 0) return;
00870
00871
00872
00873
00874 if (FindWindowById(WC_STATUS_BAR, 0) == NULL) return;
00875
00876 static byte _last_clean_month = 0;
00877
00878 if (_last_clean_month != _cur_month) {
00879 RemoveOldNewsItems();
00880 _last_clean_month = _cur_month;
00881 }
00882
00883 if (ReadyForNextItem()) MoveToNextItem();
00884 }
00885
00887 static void ShowNewsMessage(const NewsItem *ni)
00888 {
00889 assert(_total_news != 0);
00890
00891
00892 DeleteWindowById(WC_NEWS_WINDOW, 0);
00893
00894
00895 _forced_news = ni;
00896
00897 if (_forced_news != NULL) {
00898 DeleteWindowById(WC_NEWS_WINDOW, 0);
00899 ShowNewspaper(ni);
00900 }
00901 }
00902
00904 void ShowLastNewsMessage()
00905 {
00906 const NewsItem *ni = NULL;
00907 if (_total_news == 0) {
00908 return;
00909 } else if (_forced_news == NULL) {
00910
00911
00912 if (_current_news == NULL) {
00913
00914
00915 ni = _latest_news;
00916 } else {
00917 const Window *w = FindWindowById(WC_NEWS_WINDOW, 0);
00918 ni = (w == NULL || (_current_news == _oldest_news)) ? _current_news : _current_news->prev;
00919 }
00920 } else if (_forced_news == _oldest_news) {
00921
00922 ni = _latest_news;
00923 } else {
00924
00925 ni = _forced_news->prev;
00926 }
00927 bool wrap = false;
00928 for (;;) {
00929 if (_news_type_data[ni->type].GetDisplay() != ND_OFF) {
00930 ShowNewsMessage(ni);
00931 break;
00932 }
00933
00934 ni = ni->prev;
00935 if (ni == NULL) {
00936 if (wrap) break;
00937
00938 ni = _latest_news;
00939 wrap = true;
00940 }
00941 }
00942 }
00943
00944
00955 static void DrawNewsString(uint left, uint right, int y, TextColour colour, const NewsItem *ni)
00956 {
00957 char buffer[512], buffer2[512];
00958 StringID str;
00959
00960 CopyInDParam(0, ni->params, lengthof(ni->params));
00961 str = ni->string_id;
00962
00963 GetString(buffer, str, lastof(buffer));
00964
00965
00966 const char *ptr = buffer;
00967 char *dest = buffer2;
00968 WChar c_last = '\0';
00969 for (;;) {
00970 WChar c = Utf8Consume(&ptr);
00971 if (c == 0) break;
00972
00973 if (c == '\n' && c_last != '\n') {
00974 dest[0] = ' ';
00975 dest++;
00976 } else if (c == '\r') {
00977 dest[0] = dest[1] = dest[2] = dest[3] = ' ';
00978 dest += 4;
00979 } else if (IsPrintable(c)) {
00980 dest += Utf8Encode(dest, c);
00981 }
00982 c_last = c;
00983 }
00984
00985 *dest = '\0';
00986
00987 DrawString(left, right, y, buffer2, colour);
00988 }
00989
00990 struct MessageHistoryWindow : Window {
00991 static const int top_spacing;
00992 static const int bottom_spacing;
00993
00994 int line_height;
00995 int date_width;
00996
00997 Scrollbar *vscroll;
00998
00999 MessageHistoryWindow(WindowDesc *desc) : Window(desc)
01000 {
01001 this->CreateNestedTree();
01002 this->vscroll = this->GetScrollbar(WID_MH_SCROLLBAR);
01003 this->FinishInitNested();
01004 this->OnInvalidateData(0);
01005 }
01006
01007 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
01008 {
01009 if (widget == WID_MH_BACKGROUND) {
01010 this->line_height = FONT_HEIGHT_NORMAL + 2;
01011 resize->height = this->line_height;
01012
01013
01014
01015 SetDParam(0, ConvertYMDToDate(ORIGINAL_MAX_YEAR, 7, 30));
01016 this->date_width = GetStringBoundingBox(STR_SHORT_DATE).width;
01017
01018 size->height = 4 * resize->height + this->top_spacing + this->bottom_spacing;
01019 size->width = max(200u, size->width);
01020 }
01021 }
01022
01023 virtual void OnPaint()
01024 {
01025 this->OnInvalidateData(0);
01026 this->DrawWidgets();
01027 }
01028
01029 virtual void DrawWidget(const Rect &r, int widget) const
01030 {
01031 if (widget != WID_MH_BACKGROUND || _total_news == 0) return;
01032
01033
01034 NewsItem *ni = _latest_news;
01035 for (int n = this->vscroll->GetPosition(); n > 0; n--) {
01036 ni = ni->prev;
01037 if (ni == NULL) return;
01038 }
01039
01040
01041 int y = r.top + this->top_spacing;
01042 bool rtl = _current_text_dir == TD_RTL;
01043 uint date_left = rtl ? r.right - WD_FRAMERECT_RIGHT - this->date_width : r.left + WD_FRAMERECT_LEFT;
01044 uint date_right = rtl ? r.right - WD_FRAMERECT_RIGHT : r.left + WD_FRAMERECT_LEFT + this->date_width;
01045 uint news_left = rtl ? r.left + WD_FRAMERECT_LEFT : r.left + WD_FRAMERECT_LEFT + this->date_width + WD_FRAMERECT_RIGHT;
01046 uint news_right = rtl ? r.right - WD_FRAMERECT_RIGHT - this->date_width - WD_FRAMERECT_RIGHT : r.right - WD_FRAMERECT_RIGHT;
01047 for (int n = this->vscroll->GetCapacity(); n > 0; n--) {
01048 SetDParam(0, ni->date);
01049 DrawString(date_left, date_right, y, STR_SHORT_DATE);
01050
01051 DrawNewsString(news_left, news_right, y, TC_WHITE, ni);
01052 y += this->line_height;
01053
01054 ni = ni->prev;
01055 if (ni == NULL) return;
01056 }
01057 }
01058
01064 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
01065 {
01066 if (!gui_scope) return;
01067 this->vscroll->SetCount(_total_news);
01068 }
01069
01070 virtual void OnClick(Point pt, int widget, int click_count)
01071 {
01072 if (widget == WID_MH_BACKGROUND) {
01073 NewsItem *ni = _latest_news;
01074 if (ni == NULL) return;
01075
01076 for (int n = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_MH_BACKGROUND, WD_FRAMERECT_TOP, this->line_height); n > 0; n--) {
01077 ni = ni->prev;
01078 if (ni == NULL) return;
01079 }
01080
01081 ShowNewsMessage(ni);
01082 }
01083 }
01084
01085 virtual void OnResize()
01086 {
01087 this->vscroll->SetCapacity(this->GetWidget<NWidgetBase>(WID_MH_BACKGROUND)->current_y / this->line_height);
01088 }
01089 };
01090
01091 const int MessageHistoryWindow::top_spacing = WD_FRAMERECT_TOP + 4;
01092 const int MessageHistoryWindow::bottom_spacing = WD_FRAMERECT_BOTTOM;
01093
01094 static const NWidgetPart _nested_message_history[] = {
01095 NWidget(NWID_HORIZONTAL),
01096 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
01097 NWidget(WWT_CAPTION, COLOUR_BROWN), SetDataTip(STR_MESSAGE_HISTORY, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
01098 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
01099 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
01100 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
01101 EndContainer(),
01102
01103 NWidget(NWID_HORIZONTAL),
01104 NWidget(WWT_PANEL, COLOUR_BROWN, WID_MH_BACKGROUND), SetMinimalSize(200, 125), SetDataTip(0x0, STR_MESSAGE_HISTORY_TOOLTIP), SetResize(1, 12), SetScrollbar(WID_MH_SCROLLBAR),
01105 EndContainer(),
01106 NWidget(NWID_VERTICAL),
01107 NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_MH_SCROLLBAR),
01108 NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
01109 EndContainer(),
01110 EndContainer(),
01111 };
01112
01113 static WindowDesc _message_history_desc(
01114 WDP_AUTO, "list_news", 400, 140,
01115 WC_MESSAGE_HISTORY, WC_NONE,
01116 0,
01117 _nested_message_history, lengthof(_nested_message_history)
01118 );
01119
01121 void ShowMessageHistory()
01122 {
01123 DeleteWindowById(WC_MESSAGE_HISTORY, 0);
01124 new MessageHistoryWindow(&_message_history_desc);
01125 }