1 /* $Id: story_gui.cpp 26307 2014-02-06 19:50:34Z zuu $ */
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
10 /** @file story_gui.cpp GUI for stories. */
13 #include "window_gui.h"
14 #include "strings_func.h"
15 #include "date_func.h"
17 #include "story_base.h"
18 #include "core/geometry_func.hpp"
19 #include "company_func.h"
20 #include "command_func.h"
21 #include "widgets/dropdown_type.h"
22 #include "widgets/dropdown_func.h"
23 #include "sortlist_type.h"
24 #include "goal_base.h"
25 #include "viewport_func.h"
26 #include "window_func.h"
27 #include "company_base.h"
29 #include "widgets/story_widget.h"
31 #include "table/strings.h"
32 #include "table/sprites.h"
34 #include "safeguards.h"
36 typedef GUIList
<const StoryPage
*> GUIStoryPageList
;
37 typedef GUIList
<const StoryPageElement
*> GUIStoryPageElementList
;
39 struct StoryBookWindow
: Window
{
41 Scrollbar
*vscroll
; ///< Scrollbar of the page text.
43 GUIStoryPageList story_pages
; ///< Sorted list of pages.
44 GUIStoryPageElementList story_page_elements
; ///< Sorted list of page elements that belong to the current page.
45 StoryPageID selected_page_id
; ///< Pool index of selected page.
46 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.
48 static GUIStoryPageList::SortFunction
* const page_sorter_funcs
[];
49 static GUIStoryPageElementList::SortFunction
* const page_element_sorter_funcs
[];
51 /** (Re)Build story page list. */
52 void BuildStoryPageList()
54 if (this->story_pages
.NeedRebuild()) {
55 this->story_pages
.Clear();
58 FOR_ALL_STORY_PAGES(p
) {
59 if (this->IsPageAvailable(p
)) {
60 *this->story_pages
.Append() = p
;
64 this->story_pages
.Compact();
65 this->story_pages
.RebuildDone();
68 this->story_pages
.Sort();
71 /** Sort story pages by order value. */
72 static int CDECL
PageOrderSorter(const StoryPage
* const *a
, const StoryPage
* const *b
)
74 return (*a
)->sort_value
- (*b
)->sort_value
;
77 /** (Re)Build story page element list. */
78 void BuildStoryPageElementList()
80 if (this->story_page_elements
.NeedRebuild()) {
81 this->story_page_elements
.Clear();
83 const StoryPage
*p
= GetSelPage();
85 const StoryPageElement
*pe
;
86 FOR_ALL_STORY_PAGE_ELEMENTS(pe
) {
87 if (pe
->page
== p
->index
) {
88 *this->story_page_elements
.Append() = pe
;
93 this->story_page_elements
.Compact();
94 this->story_page_elements
.RebuildDone();
97 this->story_page_elements
.Sort();
100 /** Sort story page elements by order value. */
101 static int CDECL
PageElementOrderSorter(const StoryPageElement
* const *a
, const StoryPageElement
* const *b
)
103 return (*a
)->sort_value
- (*b
)->sort_value
;
107 * Checks if a given page should be visible in the story book.
108 * @param page The page to check.
109 * @return True if the page should be visible, otherwise false.
111 bool IsPageAvailable(const StoryPage
*page
) const
113 return page
->company
== INVALID_COMPANY
|| page
->company
== this->window_number
;
117 * Get instance of selected page.
118 * @return Instance of selected page or nullptr if no page is selected.
120 StoryPage
*GetSelPage() const
122 if (!_story_page_pool
.IsValidID(selected_page_id
)) return nullptr;
123 return _story_page_pool
.Get(selected_page_id
);
127 * Get the page number of selected page.
128 * @return Number of available pages before to the selected one, or -1 if no page is selected.
130 int GetSelPageNum() const
133 for (const StoryPage
*const*iter
= this->story_pages
.Begin(); iter
!= this->story_pages
.End(); iter
++) {
134 const StoryPage
*p
= *iter
;
135 if (p
->index
== this->selected_page_id
) {
144 * Check if the selected page is also the first available page.
146 bool IsFirstPageSelected()
148 /* Verify that the selected page exist. */
149 if (!_story_page_pool
.IsValidID(this->selected_page_id
)) return false;
151 return (*this->story_pages
.Begin())->index
== this->selected_page_id
;
155 * Check if the selected page is also the last available page.
157 bool IsLastPageSelected()
159 /* Verify that the selected page exist. */
160 if (!_story_page_pool
.IsValidID(this->selected_page_id
)) return false;
162 if (this->story_pages
.Length() <= 1) return true;
163 const StoryPage
*last
= *(this->story_pages
.End() - 1);
164 return last
->index
== this->selected_page_id
;
168 * Updates the content of selected page.
170 void RefreshSelectedPage()
172 /* Generate generic title if selected page have no custom title. */
173 StoryPage
*page
= this->GetSelPage();
174 if (page
!= nullptr && page
->title
== nullptr) {
175 SetDParam(0, GetSelPageNum() + 1);
176 GetString(selected_generic_title
, STR_STORY_BOOK_GENERIC_PAGE_ITEM
, lastof(selected_generic_title
));
179 this->story_page_elements
.ForceRebuild();
180 this->BuildStoryPageElementList();
182 this->vscroll
->SetCount(this->GetContentHeight());
183 this->SetWidgetDirty(WID_SB_SCROLLBAR
);
184 this->SetWidgetDirty(WID_SB_SEL_PAGE
);
185 this->SetWidgetDirty(WID_SB_PAGE_PANEL
);
189 * Selects the previous available page before the currently selected page.
191 void SelectPrevPage()
193 if (!_story_page_pool
.IsValidID(this->selected_page_id
)) return;
195 /* Find the last available page which is previous to the current selected page. */
196 const StoryPage
*last_available
;
197 last_available
= nullptr;
198 for (const StoryPage
*const*iter
= this->story_pages
.Begin(); iter
!= this->story_pages
.End(); iter
++) {
199 const StoryPage
*p
= *iter
;
200 if (p
->index
== this->selected_page_id
) {
201 if (last_available
== nullptr) return; // No previous page available.
202 this->SetSelectedPage(last_available
->index
);
210 * Selects the next available page after the currently selected page.
212 void SelectNextPage()
214 if (!_story_page_pool
.IsValidID(this->selected_page_id
)) return;
216 /* Find selected page. */
217 for (const StoryPage
*const*iter
= this->story_pages
.Begin(); iter
!= this->story_pages
.End(); iter
++) {
218 const StoryPage
*p
= *iter
;
219 if (p
->index
== this->selected_page_id
) {
220 /* Select the page after selected page. */
222 if (iter
!= this->story_pages
.End()) {
223 this->SetSelectedPage((*iter
)->index
);
231 * Builds the page selector drop down list.
233 DropDownList
*BuildDropDownList() const
235 DropDownList
*list
= new DropDownList();
237 for (const StoryPage
*const*iter
= this->story_pages
.Begin(); iter
!= this->story_pages
.End(); iter
++) {
238 const StoryPage
*p
= *iter
;
239 bool current_page
= p
->index
== this->selected_page_id
;
240 DropDownListStringItem
*item
= nullptr;
241 if (p
->title
!= nullptr) {
242 item
= new DropDownListCharStringItem(p
->title
, p
->index
, current_page
);
244 /* No custom title => use a generic page title with page number. */
245 DropDownListParamStringItem
*str_item
=
246 new DropDownListParamStringItem(STR_STORY_BOOK_GENERIC_PAGE_ITEM
, p
->index
, current_page
);
247 str_item
->SetParam(0, page_num
);
251 *list
->Append() = item
;
255 /* Check if list is empty. */
256 if (list
->Length() == 0) {
265 * Get the width available for displaying content on the page panel.
267 uint
GetAvailablePageContentWidth()
269 return this->GetWidget
<NWidgetCore
>(WID_SB_PAGE_PANEL
)->current_x
- WD_FRAMETEXT_LEFT
- WD_FRAMERECT_RIGHT
;
273 * Counts how many pixels of height that are used by Date and Title
274 * (excluding marginal after Title, as each body element has
275 * an empty row before the elment).
276 * @param max_width Available width to display content.
277 * @return the height in pixels.
279 uint
GetHeadHeight(int max_width
) const
281 StoryPage
*page
= this->GetSelPage();
282 if (page
== nullptr) return 0;
286 height
+= FONT_HEIGHT_NORMAL
; // Date always use exactly one line.
287 SetDParamStr(0, page
->title
!= nullptr ? page
->title
: this->selected_generic_title
);
288 height
+= GetStringHeight(STR_STORY_BOOK_TITLE
, max_width
);
294 * Decides which sprite to display for a given page element.
295 * @param pe The page element.
296 * @return The SpriteID of the sprite to display.
297 * @pre pe.type must be SPET_GOAL or SPET_LOCATION.
299 SpriteID
GetPageElementSprite(const StoryPageElement
&pe
) const
303 Goal
*g
= Goal::Get((GoalID
) pe
.referenced_id
);
304 if (g
== nullptr) return SPR_IMG_GOAL_BROKEN_REF
;
305 return g
->completed
? SPR_IMG_GOAL_COMPLETED
: SPR_IMG_GOAL
;
308 return SPR_IMG_VIEW_LOCATION
;
315 * Get the height in pixels used by a page element.
316 * @param pe The story page element.
317 * @param max_width Available width to display content.
318 * @return the height in pixels.
320 uint
GetPageElementHeight(const StoryPageElement
&pe
, int max_width
)
324 SetDParamStr(0, pe
.text
);
325 return GetStringHeight(STR_BLACK_RAW_STRING
, max_width
);
329 case SPET_LOCATION
: {
330 Dimension sprite_dim
= GetSpriteSize(GetPageElementSprite(pe
));
331 return sprite_dim
.height
;
341 * Get the total height of the content displayed
343 * @return the height in pixels
345 uint
GetContentHeight()
347 StoryPage
*page
= this->GetSelPage();
348 if (page
== nullptr) return 0;
349 int max_width
= GetAvailablePageContentWidth();
350 uint element_vertical_dist
= FONT_HEIGHT_NORMAL
;
353 uint height
= GetHeadHeight(max_width
);
356 for (const StoryPageElement
**iter
= this->story_page_elements
.Begin(); iter
!= this->story_page_elements
.End(); iter
++) {
357 const StoryPageElement
*pe
= *iter
;
358 height
+= element_vertical_dist
;
359 height
+= GetPageElementHeight(*pe
, max_width
);
366 * Draws a page element that is composed of a sprite to the left and a single line of
367 * text after that. These page elements are generally clickable and are thus called
369 * @param y_offset Current y_offset which will get updated when this method has completed its drawing.
370 * @param width Width of the region available for drawing.
371 * @param line_height Height of one line of text.
372 * @param action_sprite The sprite to draw.
373 * @param string_id The string id to draw.
374 * @return the number of lines.
376 void DrawActionElement(int &y_offset
, int width
, int line_height
, SpriteID action_sprite
, StringID string_id
= STR_JUST_RAW_STRING
) const
378 Dimension sprite_dim
= GetSpriteSize(action_sprite
);
379 uint element_height
= max(sprite_dim
.height
, (uint
)line_height
);
381 uint sprite_top
= y_offset
+ (element_height
- sprite_dim
.height
) / 2;
382 uint text_top
= y_offset
+ (element_height
- line_height
) / 2;
384 DrawSprite(action_sprite
, PAL_NONE
, 0, sprite_top
);
385 DrawString(sprite_dim
.width
+ WD_FRAMETEXT_LEFT
, width
, text_top
, string_id
, TC_BLACK
);
387 y_offset
+= element_height
;
391 * Internal event handler for when a page element is clicked.
392 * @param pe The clicked page element.
394 void OnPageElementClick(const StoryPageElement
& pe
)
403 ShowExtraViewPortWindow((TileIndex
)pe
.referenced_id
);
405 ScrollMainWindowToTile((TileIndex
)pe
.referenced_id
);
410 ShowGoalsList((CompanyID
)this->window_number
);
419 StoryBookWindow(WindowDesc
*desc
, WindowNumber window_number
) : Window(desc
)
421 this->CreateNestedTree();
422 this->vscroll
= this->GetScrollbar(WID_SB_SCROLLBAR
);
423 this->vscroll
->SetStepSize(FONT_HEIGHT_NORMAL
);
425 /* Initalize page sort. */
426 this->story_pages
.SetSortFuncs(StoryBookWindow::page_sorter_funcs
);
427 this->story_pages
.ForceRebuild();
428 this->BuildStoryPageList();
429 this->story_page_elements
.SetSortFuncs(StoryBookWindow::page_element_sorter_funcs
);
430 /* story_page_elements will get built by SetSelectedPage */
432 this->FinishInitNested(window_number
);
433 this->owner
= (Owner
)this->window_number
;
435 /* Initialize selected vars. */
436 this->selected_generic_title
[0] = '\0';
437 this->selected_page_id
= INVALID_STORY_PAGE
;
439 this->OnInvalidateData(-1);
443 * Updates the disabled state of the prev/next buttons.
445 void UpdatePrevNextDisabledState()
447 this->SetWidgetDisabledState(WID_SB_PREV_PAGE
, story_pages
.Length() == 0 || this->IsFirstPageSelected());
448 this->SetWidgetDisabledState(WID_SB_NEXT_PAGE
, story_pages
.Length() == 0 || this->IsLastPageSelected());
449 this->SetWidgetDirty(WID_SB_PREV_PAGE
);
450 this->SetWidgetDirty(WID_SB_NEXT_PAGE
);
454 * Sets the selected page.
455 * @param page_index pool index of the page to select.
457 void SetSelectedPage(uint16 page_index
)
459 if (this->selected_page_id
!= page_index
) {
460 this->selected_page_id
= page_index
;
461 this->RefreshSelectedPage();
462 this->UpdatePrevNextDisabledState();
466 virtual void SetStringParameters(int widget
) const
469 case WID_SB_SEL_PAGE
: {
470 StoryPage
*page
= this->GetSelPage();
471 SetDParamStr(0, page
!= nullptr && page
->title
!= nullptr ? page
->title
: this->selected_generic_title
);
475 if (this->window_number
== INVALID_COMPANY
) {
476 SetDParam(0, STR_STORY_BOOK_SPECTATOR_CAPTION
);
478 SetDParam(0, STR_STORY_BOOK_CAPTION
);
479 SetDParam(1, this->window_number
);
485 virtual void OnPaint()
487 /* Detect if content has changed height. This can happen if a
488 * multi-line text contains eg. {COMPANY} and that company is
491 if (this->vscroll
->GetCount() != this->GetContentHeight()) {
492 this->vscroll
->SetCount(this->GetContentHeight());
493 this->SetWidgetDirty(WID_SB_SCROLLBAR
);
494 this->SetWidgetDirty(WID_SB_PAGE_PANEL
);
500 virtual void DrawWidget(const Rect
&r
, int widget
) const
502 if (widget
!= WID_SB_PAGE_PANEL
) return;
504 StoryPage
*page
= this->GetSelPage();
505 if (page
== nullptr) return;
507 const int x
= r
.left
+ WD_FRAMETEXT_LEFT
;
508 const int y
= r
.top
+ WD_FRAMETEXT_TOP
;
509 const int right
= r
.right
- WD_FRAMETEXT_RIGHT
;
510 const int bottom
= r
.bottom
- WD_FRAMETEXT_BOTTOM
;
512 /* Set up a clipping region for the panel. */
513 DrawPixelInfo tmp_dpi
;
514 if (!FillDrawPixelInfo(&tmp_dpi
, x
, y
, right
- x
+ 1, bottom
- y
+ 1)) return;
516 DrawPixelInfo
*old_dpi
= _cur_dpi
;
519 /* Draw content (now coordinates given to Draw** are local to the new clipping region). */
520 int line_height
= FONT_HEIGHT_NORMAL
;
521 int y_offset
= - this->vscroll
->GetPosition();
524 if (page
->date
!= INVALID_DATE
) {
525 SetDParam(0, page
->date
);
526 DrawString(0, right
- x
, y_offset
, STR_JUST_DATE_LONG
, TC_BLACK
);
528 y_offset
+= line_height
;
531 SetDParamStr(0, page
->title
!= nullptr ? page
->title
: this->selected_generic_title
);
532 y_offset
= DrawStringMultiLine(0, right
- x
, y_offset
, bottom
- y
, STR_STORY_BOOK_TITLE
, TC_BLACK
, SA_TOP
| SA_HOR_CENTER
);
535 for (const StoryPageElement
*const*iter
= this->story_page_elements
.Begin(); iter
!= this->story_page_elements
.End(); iter
++) {
536 const StoryPageElement
*const pe
= *iter
;
537 y_offset
+= line_height
; // margin to previous element
541 SetDParamStr(0, pe
->text
);
542 y_offset
= DrawStringMultiLine(0, right
- x
, y_offset
, bottom
- y
, STR_JUST_RAW_STRING
, TC_BLACK
, SA_TOP
| SA_LEFT
);
546 Goal
*g
= Goal::Get((GoalID
) pe
->referenced_id
);
547 StringID string_id
= g
== nullptr ? STR_STORY_BOOK_INVALID_GOAL_REF
: STR_JUST_RAW_STRING
;
548 if (g
!= nullptr) SetDParamStr(0, g
->text
);
549 DrawActionElement(y_offset
, right
- x
, line_height
, GetPageElementSprite(*pe
), string_id
);
554 SetDParamStr(0, pe
->text
);
555 DrawActionElement(y_offset
, right
- x
, line_height
, GetPageElementSprite(*pe
));
558 default: NOT_REACHED();
562 /* Restore clipping region. */
566 virtual void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
)
568 if (widget
!= WID_SB_SEL_PAGE
&& widget
!= WID_SB_PAGE_PANEL
) return;
571 d
.height
= FONT_HEIGHT_NORMAL
;
575 case WID_SB_SEL_PAGE
: {
577 /* Get max title width. */
578 for (uint16 i
= 0; i
< this->story_pages
.Length(); i
++) {
579 const StoryPage
*s
= this->story_pages
[i
];
581 if (s
->title
!= nullptr) {
582 SetDParamStr(0, s
->title
);
584 SetDParamStr(0, this->selected_generic_title
);
586 Dimension title_d
= GetStringBoundingBox(STR_BLACK_RAW_STRING
);
588 if (title_d
.width
> d
.width
) {
589 d
.width
= title_d
.width
;
593 d
.width
+= padding
.width
;
594 d
.height
+= padding
.height
;
595 *size
= maxdim(*size
, d
);
599 case WID_SB_PAGE_PANEL
: {
601 d
.height
+= padding
.height
+ WD_FRAMETEXT_TOP
+ WD_FRAMETEXT_BOTTOM
;
602 *size
= maxdim(*size
, d
);
609 virtual void OnResize()
611 this->vscroll
->SetCapacityFromWidget(this, WID_SB_PAGE_PANEL
, WD_FRAMETEXT_TOP
+ WD_FRAMETEXT_BOTTOM
);
612 this->vscroll
->SetCount(this->GetContentHeight());
615 virtual void OnClick(Point pt
, int widget
, int click_count
)
618 case WID_SB_SEL_PAGE
: {
619 DropDownList
*list
= this->BuildDropDownList();
620 if (list
!= nullptr) {
621 /* Get the index of selected page. */
623 for (uint16 i
= 0; i
< this->story_pages
.Length(); i
++) {
624 const StoryPage
*p
= this->story_pages
[i
];
625 if (p
->index
== this->selected_page_id
) break;
629 ShowDropDownList(this, list
, selected
, widget
);
634 case WID_SB_PREV_PAGE
:
635 this->SelectPrevPage();
638 case WID_SB_NEXT_PAGE
:
639 this->SelectNextPage();
642 case WID_SB_PAGE_PANEL
: {
643 uint clicked_y
= this->vscroll
->GetScrolledRowFromWidget(pt
.y
, this, WID_SB_PAGE_PANEL
, WD_FRAMETEXT_TOP
);
644 uint max_width
= GetAvailablePageContentWidth();
646 /* Skip head rows. */
647 uint head_height
= this->GetHeadHeight(max_width
);
648 if (clicked_y
< head_height
) return;
650 /* Detect if a page element was clicked. */
651 uint y
= head_height
;
652 uint element_vertical_dist
= FONT_HEIGHT_NORMAL
;
653 for (const StoryPageElement
*const*iter
= this->story_page_elements
.Begin(); iter
!= this->story_page_elements
.End(); iter
++) {
654 const StoryPageElement
*const pe
= *iter
;
656 y
+= element_vertical_dist
; // margin row
658 uint content_height
= GetPageElementHeight(*pe
, max_width
);
659 if (clicked_y
>= y
&& clicked_y
< y
+ content_height
) {
660 this->OnPageElementClick(*pe
);
670 virtual void OnDropdownSelect(int widget
, int index
)
672 if (widget
!= WID_SB_SEL_PAGE
) return;
674 /* index (which is set in BuildDropDownList) is the page id. */
675 this->SetSelectedPage(index
);
679 * Some data on this window has become invalid.
680 * @param data Information about the changed data.
681 * -1 Rebuild page list and refresh current page;
682 * >= 0 Id of the page that needs to be refreshed. If it is not the current page, nothing happens.
683 * @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.
685 virtual void OnInvalidateData(int data
= 0, bool gui_scope
= true)
687 if (!gui_scope
) return;
689 /* If added/removed page, force rebuild. Sort order never change so just a
690 * re-sort is never needed.
693 this->story_pages
.ForceRebuild();
694 this->BuildStoryPageList();
696 /* Was the last page removed? */
697 if (this->story_pages
.Length() == 0) {
698 this->selected_generic_title
[0] = '\0';
701 /* Verify page selection. */
702 if (!_story_page_pool
.IsValidID(this->selected_page_id
)) {
703 this->selected_page_id
= INVALID_STORY_PAGE
;
705 if (this->selected_page_id
== INVALID_STORY_PAGE
&& this->story_pages
.Length() > 0) {
706 /* No page is selected, but there exist at least one available.
707 * => Select first page.
709 this->SetSelectedPage(this->story_pages
[0]->index
);
712 this->SetWidgetDisabledState(WID_SB_SEL_PAGE
, this->story_pages
.Length() == 0);
713 this->SetWidgetDirty(WID_SB_SEL_PAGE
);
714 this->UpdatePrevNextDisabledState();
715 } else if (data
>= 0 && this->selected_page_id
== data
) {
716 this->RefreshSelectedPage();
721 GUIStoryPageList::SortFunction
* const StoryBookWindow::page_sorter_funcs
[] = {
725 GUIStoryPageElementList::SortFunction
* const StoryBookWindow::page_element_sorter_funcs
[] = {
726 &PageElementOrderSorter
,
729 static const NWidgetPart _nested_story_book_widgets
[] = {
730 NWidget(NWID_HORIZONTAL
),
731 NWidget(WWT_CLOSEBOX
, COLOUR_BROWN
),
732 NWidget(WWT_CAPTION
, COLOUR_BROWN
, WID_SB_CAPTION
), SetDataTip(STR_JUST_STRING
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
733 NWidget(WWT_SHADEBOX
, COLOUR_BROWN
),
734 NWidget(WWT_DEFSIZEBOX
, COLOUR_BROWN
),
735 NWidget(WWT_STICKYBOX
, COLOUR_BROWN
),
737 NWidget(NWID_HORIZONTAL
), SetFill(1, 1),
738 NWidget(NWID_VERTICAL
), SetFill(1, 1),
739 NWidget(WWT_PANEL
, COLOUR_BROWN
, WID_SB_PAGE_PANEL
), SetResize(1, 1), SetScrollbar(WID_SB_SCROLLBAR
), EndContainer(),
740 NWidget(NWID_HORIZONTAL
),
741 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
),
742 NWidget(NWID_BUTTON_DROPDOWN
, COLOUR_BROWN
, WID_SB_SEL_PAGE
), SetMinimalSize(93, 12), SetFill(1, 0),
743 SetDataTip(STR_BLACK_RAW_STRING
, STR_STORY_BOOK_SEL_PAGE_TOOLTIP
), SetResize(1, 0),
744 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
),
747 NWidget(NWID_VERTICAL
), SetFill(0, 1),
748 NWidget(NWID_VSCROLLBAR
, COLOUR_BROWN
, WID_SB_SCROLLBAR
),
749 NWidget(WWT_RESIZEBOX
, COLOUR_BROWN
),
754 static WindowDesc
_story_book_desc(
755 WDP_CENTER
, "view_story", 400, 300,
756 WC_STORY_BOOK
, WC_NONE
,
758 _nested_story_book_widgets
, lengthof(_nested_story_book_widgets
)
762 * Raise or create the story book window for \a company, at page \a page_id.
763 * @param company 'Owner' of the story book, may be #INVALID_COMPANY.
764 * @param page_id Page to open, may be #INVALID_STORY_PAGE.
766 void ShowStoryBook(CompanyID company
, uint16 page_id
)
768 if (!Company::IsValidID(company
)) company
= (CompanyID
)INVALID_COMPANY
;
770 StoryBookWindow
*w
= AllocateWindowDescFront
<StoryBookWindow
>(&_story_book_desc
, company
, true);
771 if (page_id
!= INVALID_STORY_PAGE
) w
->SetSelectedPage(page_id
);