Codechange: Use cached town, station, industry names for list window sorting
[openttd-github.git] / src / story_gui.cpp
blob677d88bba892eb4f992a21e0865973f85ad6597c
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file story_gui.cpp GUI for stories. */
10 #include "stdafx.h"
11 #include "window_gui.h"
12 #include "strings_func.h"
13 #include "date_func.h"
14 #include "gui.h"
15 #include "story_base.h"
16 #include "core/geometry_func.hpp"
17 #include "company_func.h"
18 #include "command_func.h"
19 #include "widgets/dropdown_type.h"
20 #include "widgets/dropdown_func.h"
21 #include "sortlist_type.h"
22 #include "goal_base.h"
23 #include "viewport_func.h"
24 #include "window_func.h"
25 #include "company_base.h"
27 #include "widgets/story_widget.h"
29 #include "table/strings.h"
30 #include "table/sprites.h"
32 #include "safeguards.h"
34 typedef GUIList<const StoryPage*> GUIStoryPageList;
35 typedef GUIList<const StoryPageElement*> GUIStoryPageElementList;
37 struct StoryBookWindow : Window {
38 protected:
39 Scrollbar *vscroll; ///< Scrollbar of the page text.
41 GUIStoryPageList story_pages; ///< Sorted list of pages.
42 GUIStoryPageElementList story_page_elements; ///< Sorted list of page elements that belong to the current page.
43 StoryPageID selected_page_id; ///< Pool index of selected page.
44 char selected_generic_title[255]; ///< If the selected page doesn't have a custom title, this buffer is used to store a generic page title.
46 static GUIStoryPageList::SortFunction * const page_sorter_funcs[];
47 static GUIStoryPageElementList::SortFunction * const page_element_sorter_funcs[];
49 /** (Re)Build story page list. */
50 void BuildStoryPageList()
52 if (this->story_pages.NeedRebuild()) {
53 this->story_pages.clear();
55 for (const StoryPage *p : StoryPage::Iterate()) {
56 if (this->IsPageAvailable(p)) {
57 this->story_pages.push_back(p);
61 this->story_pages.shrink_to_fit();
62 this->story_pages.RebuildDone();
65 this->story_pages.Sort();
68 /** Sort story pages by order value. */
69 static bool PageOrderSorter(const StoryPage * const &a, const StoryPage * const &b)
71 return a->sort_value < b->sort_value;
74 /** (Re)Build story page element list. */
75 void BuildStoryPageElementList()
77 if (this->story_page_elements.NeedRebuild()) {
78 this->story_page_elements.clear();
80 const StoryPage *p = GetSelPage();
81 if (p != nullptr) {
82 for (const StoryPageElement *pe : StoryPageElement::Iterate()) {
83 if (pe->page == p->index) {
84 this->story_page_elements.push_back(pe);
89 this->story_page_elements.shrink_to_fit();
90 this->story_page_elements.RebuildDone();
93 this->story_page_elements.Sort();
96 /** Sort story page elements by order value. */
97 static bool PageElementOrderSorter(const StoryPageElement * const &a, const StoryPageElement * const &b)
99 return a->sort_value < b->sort_value;
103 * Checks if a given page should be visible in the story book.
104 * @param page The page to check.
105 * @return True if the page should be visible, otherwise false.
107 bool IsPageAvailable(const StoryPage *page) const
109 return page->company == INVALID_COMPANY || page->company == this->window_number;
113 * Get instance of selected page.
114 * @return Instance of selected page or nullptr if no page is selected.
116 StoryPage *GetSelPage() const
118 if (!_story_page_pool.IsValidID(selected_page_id)) return nullptr;
119 return _story_page_pool.Get(selected_page_id);
123 * Get the page number of selected page.
124 * @return Number of available pages before to the selected one, or -1 if no page is selected.
126 int GetSelPageNum() const
128 int page_number = 0;
129 for (const StoryPage *p : this->story_pages) {
130 if (p->index == this->selected_page_id) {
131 return page_number;
133 page_number++;
135 return -1;
139 * Check if the selected page is also the first available page.
141 bool IsFirstPageSelected()
143 /* Verify that the selected page exist. */
144 if (!_story_page_pool.IsValidID(this->selected_page_id)) return false;
146 return this->story_pages.front()->index == this->selected_page_id;
150 * Check if the selected page is also the last available page.
152 bool IsLastPageSelected()
154 /* Verify that the selected page exist. */
155 if (!_story_page_pool.IsValidID(this->selected_page_id)) return false;
157 if (this->story_pages.size() <= 1) return true;
158 const StoryPage *last = this->story_pages.back();
159 return last->index == this->selected_page_id;
163 * Updates the content of selected page.
165 void RefreshSelectedPage()
167 /* Generate generic title if selected page have no custom title. */
168 StoryPage *page = this->GetSelPage();
169 if (page != nullptr && page->title == nullptr) {
170 SetDParam(0, GetSelPageNum() + 1);
171 GetString(selected_generic_title, STR_STORY_BOOK_GENERIC_PAGE_ITEM, lastof(selected_generic_title));
174 this->story_page_elements.ForceRebuild();
175 this->BuildStoryPageElementList();
177 this->vscroll->SetCount(this->GetContentHeight());
178 this->SetWidgetDirty(WID_SB_SCROLLBAR);
179 this->SetWidgetDirty(WID_SB_SEL_PAGE);
180 this->SetWidgetDirty(WID_SB_PAGE_PANEL);
184 * Selects the previous available page before the currently selected page.
186 void SelectPrevPage()
188 if (!_story_page_pool.IsValidID(this->selected_page_id)) return;
190 /* Find the last available page which is previous to the current selected page. */
191 const StoryPage *last_available;
192 last_available = nullptr;
193 for (const StoryPage *p : this->story_pages) {
194 if (p->index == this->selected_page_id) {
195 if (last_available == nullptr) return; // No previous page available.
196 this->SetSelectedPage(last_available->index);
197 return;
199 last_available = p;
204 * Selects the next available page after the currently selected page.
206 void SelectNextPage()
208 if (!_story_page_pool.IsValidID(this->selected_page_id)) return;
210 /* Find selected page. */
211 for (auto iter = this->story_pages.begin(); iter != this->story_pages.end(); iter++) {
212 const StoryPage *p = *iter;
213 if (p->index == this->selected_page_id) {
214 /* Select the page after selected page. */
215 iter++;
216 if (iter != this->story_pages.end()) {
217 this->SetSelectedPage((*iter)->index);
219 return;
225 * Builds the page selector drop down list.
227 DropDownList BuildDropDownList() const
229 DropDownList list;
230 uint16 page_num = 1;
231 for (const StoryPage *p : this->story_pages) {
232 bool current_page = p->index == this->selected_page_id;
233 DropDownListStringItem *item = nullptr;
234 if (p->title != nullptr) {
235 item = new DropDownListCharStringItem(p->title, p->index, current_page);
236 } else {
237 /* No custom title => use a generic page title with page number. */
238 DropDownListParamStringItem *str_item =
239 new DropDownListParamStringItem(STR_STORY_BOOK_GENERIC_PAGE_ITEM, p->index, current_page);
240 str_item->SetParam(0, page_num);
241 item = str_item;
244 list.emplace_back(item);
245 page_num++;
248 return list;
252 * Get the width available for displaying content on the page panel.
254 uint GetAvailablePageContentWidth()
256 return this->GetWidget<NWidgetCore>(WID_SB_PAGE_PANEL)->current_x - WD_FRAMETEXT_LEFT - WD_FRAMERECT_RIGHT;
260 * Counts how many pixels of height that are used by Date and Title
261 * (excluding marginal after Title, as each body element has
262 * an empty row before the element).
263 * @param max_width Available width to display content.
264 * @return the height in pixels.
266 uint GetHeadHeight(int max_width) const
268 StoryPage *page = this->GetSelPage();
269 if (page == nullptr) return 0;
270 int height = 0;
272 /* Title lines */
273 height += FONT_HEIGHT_NORMAL; // Date always use exactly one line.
274 SetDParamStr(0, page->title != nullptr ? page->title : this->selected_generic_title);
275 height += GetStringHeight(STR_STORY_BOOK_TITLE, max_width);
277 return height;
281 * Decides which sprite to display for a given page element.
282 * @param pe The page element.
283 * @return The SpriteID of the sprite to display.
284 * @pre pe.type must be SPET_GOAL or SPET_LOCATION.
286 SpriteID GetPageElementSprite(const StoryPageElement &pe) const
288 switch (pe.type) {
289 case SPET_GOAL: {
290 Goal *g = Goal::Get((GoalID) pe.referenced_id);
291 if (g == nullptr) return SPR_IMG_GOAL_BROKEN_REF;
292 return g->completed ? SPR_IMG_GOAL_COMPLETED : SPR_IMG_GOAL;
294 case SPET_LOCATION:
295 return SPR_IMG_VIEW_LOCATION;
296 default:
297 NOT_REACHED();
302 * Get the height in pixels used by a page element.
303 * @param pe The story page element.
304 * @param max_width Available width to display content.
305 * @return the height in pixels.
307 uint GetPageElementHeight(const StoryPageElement &pe, int max_width)
309 switch (pe.type) {
310 case SPET_TEXT:
311 SetDParamStr(0, pe.text);
312 return GetStringHeight(STR_BLACK_RAW_STRING, max_width);
313 break;
315 case SPET_GOAL:
316 case SPET_LOCATION: {
317 Dimension sprite_dim = GetSpriteSize(GetPageElementSprite(pe));
318 return sprite_dim.height;
319 break;
321 default:
322 NOT_REACHED();
324 return 0;
328 * Get the total height of the content displayed
329 * in this window.
330 * @return the height in pixels
332 uint GetContentHeight()
334 StoryPage *page = this->GetSelPage();
335 if (page == nullptr) return 0;
336 int max_width = GetAvailablePageContentWidth();
337 uint element_vertical_dist = FONT_HEIGHT_NORMAL;
339 /* Head */
340 uint height = GetHeadHeight(max_width);
342 /* Body */
343 for (const StoryPageElement *pe : this->story_page_elements) {
344 height += element_vertical_dist;
345 height += GetPageElementHeight(*pe, max_width);
348 return height;
352 * Draws a page element that is composed of a sprite to the left and a single line of
353 * text after that. These page elements are generally clickable and are thus called
354 * action elements.
355 * @param y_offset Current y_offset which will get updated when this method has completed its drawing.
356 * @param width Width of the region available for drawing.
357 * @param line_height Height of one line of text.
358 * @param action_sprite The sprite to draw.
359 * @param string_id The string id to draw.
360 * @return the number of lines.
362 void DrawActionElement(int &y_offset, int width, int line_height, SpriteID action_sprite, StringID string_id = STR_JUST_RAW_STRING) const
364 Dimension sprite_dim = GetSpriteSize(action_sprite);
365 uint element_height = max(sprite_dim.height, (uint)line_height);
367 uint sprite_top = y_offset + (element_height - sprite_dim.height) / 2;
368 uint text_top = y_offset + (element_height - line_height) / 2;
370 DrawSprite(action_sprite, PAL_NONE, 0, sprite_top);
371 DrawString(sprite_dim.width + WD_FRAMETEXT_LEFT, width, text_top, string_id, TC_BLACK);
373 y_offset += element_height;
377 * Internal event handler for when a page element is clicked.
378 * @param pe The clicked page element.
380 void OnPageElementClick(const StoryPageElement& pe)
382 switch (pe.type) {
383 case SPET_TEXT:
384 /* Do nothing. */
385 break;
387 case SPET_LOCATION:
388 if (_ctrl_pressed) {
389 ShowExtraViewPortWindow((TileIndex)pe.referenced_id);
390 } else {
391 ScrollMainWindowToTile((TileIndex)pe.referenced_id);
393 break;
395 case SPET_GOAL:
396 ShowGoalsList((CompanyID)this->window_number);
397 break;
399 default:
400 NOT_REACHED();
404 public:
405 StoryBookWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc)
407 this->CreateNestedTree();
408 this->vscroll = this->GetScrollbar(WID_SB_SCROLLBAR);
409 this->vscroll->SetStepSize(FONT_HEIGHT_NORMAL);
411 /* Initialize page sort. */
412 this->story_pages.SetSortFuncs(StoryBookWindow::page_sorter_funcs);
413 this->story_pages.ForceRebuild();
414 this->BuildStoryPageList();
415 this->story_page_elements.SetSortFuncs(StoryBookWindow::page_element_sorter_funcs);
416 /* story_page_elements will get built by SetSelectedPage */
418 this->FinishInitNested(window_number);
419 this->owner = (Owner)this->window_number;
421 /* Initialize selected vars. */
422 this->selected_generic_title[0] = '\0';
423 this->selected_page_id = INVALID_STORY_PAGE;
425 this->OnInvalidateData(-1);
429 * Updates the disabled state of the prev/next buttons.
431 void UpdatePrevNextDisabledState()
433 this->SetWidgetDisabledState(WID_SB_PREV_PAGE, story_pages.size() == 0 || this->IsFirstPageSelected());
434 this->SetWidgetDisabledState(WID_SB_NEXT_PAGE, story_pages.size() == 0 || this->IsLastPageSelected());
435 this->SetWidgetDirty(WID_SB_PREV_PAGE);
436 this->SetWidgetDirty(WID_SB_NEXT_PAGE);
440 * Sets the selected page.
441 * @param page_index pool index of the page to select.
443 void SetSelectedPage(uint16 page_index)
445 if (this->selected_page_id != page_index) {
446 this->selected_page_id = page_index;
447 this->RefreshSelectedPage();
448 this->UpdatePrevNextDisabledState();
452 void SetStringParameters(int widget) const override
454 switch (widget) {
455 case WID_SB_SEL_PAGE: {
456 StoryPage *page = this->GetSelPage();
457 SetDParamStr(0, page != nullptr && page->title != nullptr ? page->title : this->selected_generic_title);
458 break;
460 case WID_SB_CAPTION:
461 if (this->window_number == INVALID_COMPANY) {
462 SetDParam(0, STR_STORY_BOOK_SPECTATOR_CAPTION);
463 } else {
464 SetDParam(0, STR_STORY_BOOK_CAPTION);
465 SetDParam(1, this->window_number);
467 break;
471 void OnPaint() override
473 /* Detect if content has changed height. This can happen if a
474 * multi-line text contains eg. {COMPANY} and that company is
475 * renamed.
477 if (this->vscroll->GetCount() != this->GetContentHeight()) {
478 this->vscroll->SetCount(this->GetContentHeight());
479 this->SetWidgetDirty(WID_SB_SCROLLBAR);
480 this->SetWidgetDirty(WID_SB_PAGE_PANEL);
483 this->DrawWidgets();
486 void DrawWidget(const Rect &r, int widget) const override
488 if (widget != WID_SB_PAGE_PANEL) return;
490 StoryPage *page = this->GetSelPage();
491 if (page == nullptr) return;
493 const int x = r.left + WD_FRAMETEXT_LEFT;
494 const int y = r.top + WD_FRAMETEXT_TOP;
495 const int right = r.right - WD_FRAMETEXT_RIGHT;
496 const int bottom = r.bottom - WD_FRAMETEXT_BOTTOM;
498 /* Set up a clipping region for the panel. */
499 DrawPixelInfo tmp_dpi;
500 if (!FillDrawPixelInfo(&tmp_dpi, x, y, right - x + 1, bottom - y + 1)) return;
502 DrawPixelInfo *old_dpi = _cur_dpi;
503 _cur_dpi = &tmp_dpi;
505 /* Draw content (now coordinates given to Draw** are local to the new clipping region). */
506 int line_height = FONT_HEIGHT_NORMAL;
507 int y_offset = - this->vscroll->GetPosition();
509 /* Date */
510 if (page->date != INVALID_DATE) {
511 SetDParam(0, page->date);
512 DrawString(0, right - x, y_offset, STR_JUST_DATE_LONG, TC_BLACK);
514 y_offset += line_height;
516 /* Title */
517 SetDParamStr(0, page->title != nullptr ? page->title : this->selected_generic_title);
518 y_offset = DrawStringMultiLine(0, right - x, y_offset, bottom - y, STR_STORY_BOOK_TITLE, TC_BLACK, SA_TOP | SA_HOR_CENTER);
520 /* Page elements */
521 for (const StoryPageElement *const pe : this->story_page_elements) {
522 y_offset += line_height; // margin to previous element
524 switch (pe->type) {
525 case SPET_TEXT:
526 SetDParamStr(0, pe->text);
527 y_offset = DrawStringMultiLine(0, right - x, y_offset, bottom - y, STR_JUST_RAW_STRING, TC_BLACK, SA_TOP | SA_LEFT);
528 break;
530 case SPET_GOAL: {
531 Goal *g = Goal::Get((GoalID) pe->referenced_id);
532 StringID string_id = g == nullptr ? STR_STORY_BOOK_INVALID_GOAL_REF : STR_JUST_RAW_STRING;
533 if (g != nullptr) SetDParamStr(0, g->text);
534 DrawActionElement(y_offset, right - x, line_height, GetPageElementSprite(*pe), string_id);
535 break;
538 case SPET_LOCATION:
539 SetDParamStr(0, pe->text);
540 DrawActionElement(y_offset, right - x, line_height, GetPageElementSprite(*pe));
541 break;
543 default: NOT_REACHED();
547 /* Restore clipping region. */
548 _cur_dpi = old_dpi;
551 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
553 if (widget != WID_SB_SEL_PAGE && widget != WID_SB_PAGE_PANEL) return;
555 Dimension d;
556 d.height = FONT_HEIGHT_NORMAL;
557 d.width = 0;
559 switch (widget) {
560 case WID_SB_SEL_PAGE: {
562 /* Get max title width. */
563 for (uint16 i = 0; i < this->story_pages.size(); i++) {
564 const StoryPage *s = this->story_pages[i];
566 if (s->title != nullptr) {
567 SetDParamStr(0, s->title);
568 } else {
569 SetDParamStr(0, this->selected_generic_title);
571 Dimension title_d = GetStringBoundingBox(STR_BLACK_RAW_STRING);
573 if (title_d.width > d.width) {
574 d.width = title_d.width;
578 d.width += padding.width;
579 d.height += padding.height;
580 *size = maxdim(*size, d);
581 break;
584 case WID_SB_PAGE_PANEL: {
585 d.height *= 5;
586 d.height += padding.height + WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM;
587 *size = maxdim(*size, d);
588 break;
594 void OnResize() override
596 this->vscroll->SetCapacityFromWidget(this, WID_SB_PAGE_PANEL, WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM);
597 this->vscroll->SetCount(this->GetContentHeight());
600 void OnClick(Point pt, int widget, int click_count) override
602 switch (widget) {
603 case WID_SB_SEL_PAGE: {
604 DropDownList list = this->BuildDropDownList();
605 if (!list.empty()) {
606 /* Get the index of selected page. */
607 int selected = 0;
608 for (uint16 i = 0; i < this->story_pages.size(); i++) {
609 const StoryPage *p = this->story_pages[i];
610 if (p->index == this->selected_page_id) break;
611 selected++;
614 ShowDropDownList(this, std::move(list), selected, widget);
616 break;
619 case WID_SB_PREV_PAGE:
620 this->SelectPrevPage();
621 break;
623 case WID_SB_NEXT_PAGE:
624 this->SelectNextPage();
625 break;
627 case WID_SB_PAGE_PANEL: {
628 uint clicked_y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SB_PAGE_PANEL, WD_FRAMETEXT_TOP);
629 uint max_width = GetAvailablePageContentWidth();
631 /* Skip head rows. */
632 uint head_height = this->GetHeadHeight(max_width);
633 if (clicked_y < head_height) return;
635 /* Detect if a page element was clicked. */
636 uint y = head_height;
637 uint element_vertical_dist = FONT_HEIGHT_NORMAL;
638 for (const StoryPageElement *const pe : this->story_page_elements) {
640 y += element_vertical_dist; // margin row
642 uint content_height = GetPageElementHeight(*pe, max_width);
643 if (clicked_y >= y && clicked_y < y + content_height) {
644 this->OnPageElementClick(*pe);
645 return;
648 y += content_height;
654 void OnDropdownSelect(int widget, int index) override
656 if (widget != WID_SB_SEL_PAGE) return;
658 /* index (which is set in BuildDropDownList) is the page id. */
659 this->SetSelectedPage(index);
663 * Some data on this window has become invalid.
664 * @param data Information about the changed data.
665 * -1 Rebuild page list and refresh current page;
666 * >= 0 Id of the page that needs to be refreshed. If it is not the current page, nothing happens.
667 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
669 void OnInvalidateData(int data = 0, bool gui_scope = true) override
671 if (!gui_scope) return;
673 /* If added/removed page, force rebuild. Sort order never change so just a
674 * re-sort is never needed.
676 if (data == -1) {
677 this->story_pages.ForceRebuild();
678 this->BuildStoryPageList();
680 /* Was the last page removed? */
681 if (this->story_pages.size() == 0) {
682 this->selected_generic_title[0] = '\0';
685 /* Verify page selection. */
686 if (!_story_page_pool.IsValidID(this->selected_page_id)) {
687 this->selected_page_id = INVALID_STORY_PAGE;
689 if (this->selected_page_id == INVALID_STORY_PAGE && this->story_pages.size() > 0) {
690 /* No page is selected, but there exist at least one available.
691 * => Select first page.
693 this->SetSelectedPage(this->story_pages[0]->index);
696 this->SetWidgetDisabledState(WID_SB_SEL_PAGE, this->story_pages.size() == 0);
697 this->SetWidgetDirty(WID_SB_SEL_PAGE);
698 this->UpdatePrevNextDisabledState();
699 } else if (data >= 0 && this->selected_page_id == data) {
700 this->RefreshSelectedPage();
705 GUIStoryPageList::SortFunction * const StoryBookWindow::page_sorter_funcs[] = {
706 &PageOrderSorter,
709 GUIStoryPageElementList::SortFunction * const StoryBookWindow::page_element_sorter_funcs[] = {
710 &PageElementOrderSorter,
713 static const NWidgetPart _nested_story_book_widgets[] = {
714 NWidget(NWID_HORIZONTAL),
715 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
716 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_SB_CAPTION), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
717 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
718 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
719 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
720 EndContainer(),
721 NWidget(NWID_HORIZONTAL), SetFill(1, 1),
722 NWidget(NWID_VERTICAL), SetFill(1, 1),
723 NWidget(WWT_PANEL, COLOUR_BROWN, WID_SB_PAGE_PANEL), SetResize(1, 1), SetScrollbar(WID_SB_SCROLLBAR), EndContainer(),
724 NWidget(NWID_HORIZONTAL),
725 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_SB_PREV_PAGE), SetMinimalSize(100, 0), SetFill(0, 0), SetDataTip(STR_STORY_BOOK_PREV_PAGE, STR_STORY_BOOK_PREV_PAGE_TOOLTIP),
726 NWidget(NWID_BUTTON_DROPDOWN, COLOUR_BROWN, WID_SB_SEL_PAGE), SetMinimalSize(93, 12), SetFill(1, 0),
727 SetDataTip(STR_BLACK_RAW_STRING, STR_STORY_BOOK_SEL_PAGE_TOOLTIP), SetResize(1, 0),
728 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_SB_NEXT_PAGE), SetMinimalSize(100, 0), SetFill(0, 0), SetDataTip(STR_STORY_BOOK_NEXT_PAGE, STR_STORY_BOOK_NEXT_PAGE_TOOLTIP),
729 EndContainer(),
730 EndContainer(),
731 NWidget(NWID_VERTICAL), SetFill(0, 1),
732 NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_SB_SCROLLBAR),
733 NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
734 EndContainer(),
735 EndContainer(),
738 static WindowDesc _story_book_desc(
739 WDP_CENTER, "view_story", 400, 300,
740 WC_STORY_BOOK, WC_NONE,
742 _nested_story_book_widgets, lengthof(_nested_story_book_widgets)
746 * Raise or create the story book window for \a company, at page \a page_id.
747 * @param company 'Owner' of the story book, may be #INVALID_COMPANY.
748 * @param page_id Page to open, may be #INVALID_STORY_PAGE.
750 void ShowStoryBook(CompanyID company, uint16 page_id)
752 if (!Company::IsValidID(company)) company = (CompanyID)INVALID_COMPANY;
754 StoryBookWindow *w = AllocateWindowDescFront<StoryBookWindow>(&_story_book_desc, company, true);
755 if (page_id != INVALID_STORY_PAGE) w->SetSelectedPage(page_id);