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/>.
8 /** @file story_gui.cpp GUI for stories. */
11 #include "window_gui.h"
12 #include "strings_func.h"
13 #include "date_func.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
{
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();
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
129 for (const StoryPage
*p
: this->story_pages
) {
130 if (p
->index
== this->selected_page_id
) {
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
);
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. */
216 if (iter
!= this->story_pages
.end()) {
217 this->SetSelectedPage((*iter
)->index
);
225 * Builds the page selector drop down list.
227 DropDownList
BuildDropDownList() const
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
);
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
);
244 list
.emplace_back(item
);
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;
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
);
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
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
;
295 return SPR_IMG_VIEW_LOCATION
;
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
)
311 SetDParamStr(0, pe
.text
);
312 return GetStringHeight(STR_BLACK_RAW_STRING
, max_width
);
316 case SPET_LOCATION
: {
317 Dimension sprite_dim
= GetSpriteSize(GetPageElementSprite(pe
));
318 return sprite_dim
.height
;
328 * Get the total height of the content displayed
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
;
340 uint height
= GetHeadHeight(max_width
);
343 for (const StoryPageElement
*pe
: this->story_page_elements
) {
344 height
+= element_vertical_dist
;
345 height
+= GetPageElementHeight(*pe
, max_width
);
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
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
)
389 ShowExtraViewPortWindow((TileIndex
)pe
.referenced_id
);
391 ScrollMainWindowToTile((TileIndex
)pe
.referenced_id
);
396 ShowGoalsList((CompanyID
)this->window_number
);
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
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
);
461 if (this->window_number
== INVALID_COMPANY
) {
462 SetDParam(0, STR_STORY_BOOK_SPECTATOR_CAPTION
);
464 SetDParam(0, STR_STORY_BOOK_CAPTION
);
465 SetDParam(1, this->window_number
);
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
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
);
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
;
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();
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
;
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
);
521 for (const StoryPageElement
*const pe
: this->story_page_elements
) {
522 y_offset
+= line_height
; // margin to previous element
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
);
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
);
539 SetDParamStr(0, pe
->text
);
540 DrawActionElement(y_offset
, right
- x
, line_height
, GetPageElementSprite(*pe
));
543 default: NOT_REACHED();
547 /* Restore clipping region. */
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;
556 d
.height
= FONT_HEIGHT_NORMAL
;
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
);
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
);
584 case WID_SB_PAGE_PANEL
: {
586 d
.height
+= padding
.height
+ WD_FRAMETEXT_TOP
+ WD_FRAMETEXT_BOTTOM
;
587 *size
= maxdim(*size
, d
);
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
603 case WID_SB_SEL_PAGE
: {
604 DropDownList list
= this->BuildDropDownList();
606 /* Get the index of selected page. */
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;
614 ShowDropDownList(this, std::move(list
), selected
, widget
);
619 case WID_SB_PREV_PAGE
:
620 this->SelectPrevPage();
623 case WID_SB_NEXT_PAGE
:
624 this->SelectNextPage();
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
);
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.
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
[] = {
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
),
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
),
731 NWidget(NWID_VERTICAL
), SetFill(0, 1),
732 NWidget(NWID_VSCROLLBAR
, COLOUR_BROWN
, WID_SB_SCROLLBAR
),
733 NWidget(WWT_RESIZEBOX
, COLOUR_BROWN
),
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
);