story_gui.cpp

Go to the documentation of this file.
00001 /* $Id: story_gui.cpp 26307 2014-02-06 19:50:34Z zuu $ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * 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.
00006  * 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.
00007  * 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/>.
00008  */
00009 
00012 #include "stdafx.h"
00013 #include "window_gui.h"
00014 #include "strings_func.h"
00015 #include "date_func.h"
00016 #include "gui.h"
00017 #include "story_base.h"
00018 #include "core/geometry_func.hpp"
00019 #include "company_func.h"
00020 #include "command_func.h"
00021 #include "widgets/dropdown_type.h"
00022 #include "widgets/dropdown_func.h"
00023 #include "sortlist_type.h"
00024 #include "goal_base.h"
00025 #include "viewport_func.h"
00026 #include "window_func.h"
00027 #include "company_base.h"
00028 
00029 #include "widgets/story_widget.h"
00030 
00031 #include "table/strings.h"
00032 #include "table/sprites.h"
00033 
00034 typedef GUIList<const StoryPage*> GUIStoryPageList;
00035 typedef GUIList<const StoryPageElement*> GUIStoryPageElementList;
00036 
00037 struct StoryBookWindow : Window {
00038 protected:
00039   Scrollbar *vscroll;                
00040 
00041   GUIStoryPageList story_pages;      
00042   GUIStoryPageElementList story_page_elements; 
00043   StoryPageID selected_page_id;      
00044   char selected_generic_title[255];  
00045 
00046   static GUIStoryPageList::SortFunction * const page_sorter_funcs[];
00047   static GUIStoryPageElementList::SortFunction * const page_element_sorter_funcs[];
00048 
00050   void BuildStoryPageList()
00051   {
00052     if (this->story_pages.NeedRebuild()) {
00053       this->story_pages.Clear();
00054 
00055       const StoryPage *p;
00056       FOR_ALL_STORY_PAGES(p) {
00057         if (this->IsPageAvailable(p)) {
00058           *this->story_pages.Append() = p;
00059         }
00060       }
00061 
00062       this->story_pages.Compact();
00063       this->story_pages.RebuildDone();
00064     }
00065 
00066     this->story_pages.Sort();
00067   }
00068 
00070   static int CDECL PageOrderSorter(const StoryPage * const *a, const StoryPage * const *b)
00071   {
00072     return (*a)->sort_value - (*b)->sort_value;
00073   }
00074 
00076   void BuildStoryPageElementList()
00077   {
00078     if (this->story_page_elements.NeedRebuild()) {
00079       this->story_page_elements.Clear();
00080 
00081       const StoryPage *p = GetSelPage();
00082       if (p != NULL) {
00083         const StoryPageElement *pe;
00084         FOR_ALL_STORY_PAGE_ELEMENTS(pe) {
00085           if (pe->page == p->index) {
00086             *this->story_page_elements.Append() = pe;
00087           }
00088         }
00089       }
00090 
00091       this->story_page_elements.Compact();
00092       this->story_page_elements.RebuildDone();
00093     }
00094 
00095     this->story_page_elements.Sort();
00096   }
00097 
00099   static int CDECL PageElementOrderSorter(const StoryPageElement * const *a, const StoryPageElement * const *b)
00100   {
00101     return (*a)->sort_value - (*b)->sort_value;
00102   }
00103 
00104   /*
00105    * Checks if a given page should be visible in the story book.
00106    * @param page The page to check.
00107    * @return True if the page should be visible, otherwise false.
00108    */
00109   bool IsPageAvailable(const StoryPage *page) const
00110   {
00111     return page->company == INVALID_COMPANY || page->company == this->window_number;
00112   }
00113 
00118   StoryPage *GetSelPage() const
00119   {
00120     if (!_story_page_pool.IsValidID(selected_page_id)) return NULL;
00121     return _story_page_pool.Get(selected_page_id);
00122   }
00123 
00128   int GetSelPageNum() const
00129   {
00130     int page_number = 0;
00131     for (const StoryPage *const*iter = this->story_pages.Begin(); iter != this->story_pages.End(); iter++) {
00132       const StoryPage *p = *iter;
00133       if (p->index == this->selected_page_id) {
00134         return page_number;
00135       }
00136       page_number++;
00137     }
00138     return -1;
00139   }
00140 
00144   bool IsFirstPageSelected()
00145   {
00146     /* Verify that the selected page exist. */
00147     if (!_story_page_pool.IsValidID(this->selected_page_id)) return false;
00148 
00149     return (*this->story_pages.Begin())->index == this->selected_page_id;
00150   }
00151 
00155   bool IsLastPageSelected()
00156   {
00157     /* Verify that the selected page exist. */
00158     if (!_story_page_pool.IsValidID(this->selected_page_id)) return false;
00159 
00160     if (this->story_pages.Length() <= 1) return true;
00161     const StoryPage *last = *(this->story_pages.End() - 1);
00162     return last->index == this->selected_page_id;
00163   }
00164 
00168   void RefreshSelectedPage()
00169   {
00170     /* Generate generic title if selected page have no custom title. */
00171     StoryPage *page = this->GetSelPage();
00172     if (page != NULL && page->title == NULL) {
00173       SetDParam(0, GetSelPageNum() + 1);
00174       GetString(selected_generic_title, STR_STORY_BOOK_GENERIC_PAGE_ITEM, lastof(selected_generic_title));
00175     }
00176 
00177     this->story_page_elements.ForceRebuild();
00178     this->BuildStoryPageElementList();
00179 
00180     this->vscroll->SetCount(this->GetContentHeight());
00181     this->SetWidgetDirty(WID_SB_SCROLLBAR);
00182     this->SetWidgetDirty(WID_SB_SEL_PAGE);
00183     this->SetWidgetDirty(WID_SB_PAGE_PANEL);
00184   }
00185 
00189   void SelectPrevPage()
00190   {
00191     if (!_story_page_pool.IsValidID(this->selected_page_id)) return;
00192 
00193     /* Find the last available page which is previous to the current selected page. */
00194     const StoryPage *last_available;
00195     last_available = NULL;
00196     for (const StoryPage *const*iter = this->story_pages.Begin(); iter != this->story_pages.End(); iter++) {
00197       const StoryPage *p = *iter;
00198       if (p->index == this->selected_page_id) {
00199         if (last_available == NULL) return; // No previous page available.
00200         this->SetSelectedPage(last_available->index);
00201         return;
00202       }
00203       last_available = p;
00204     }
00205   }
00206 
00210   void SelectNextPage()
00211   {
00212     if (!_story_page_pool.IsValidID(this->selected_page_id)) return;
00213 
00214     /* Find selected page. */
00215     for (const StoryPage *const*iter = this->story_pages.Begin(); iter != this->story_pages.End(); iter++) {
00216       const StoryPage *p = *iter;
00217       if (p->index == this->selected_page_id) {
00218         /* Select the page after selected page. */
00219         iter++;
00220         if (iter != this->story_pages.End()) {
00221           this->SetSelectedPage((*iter)->index);
00222         }
00223         return;
00224       }
00225     }
00226   }
00227 
00231   DropDownList *BuildDropDownList() const
00232   {
00233     DropDownList *list = new DropDownList();
00234     uint16 page_num = 1;
00235     for (const StoryPage *const*iter = this->story_pages.Begin(); iter != this->story_pages.End(); iter++) {
00236       const StoryPage *p = *iter;
00237       bool current_page = p->index == this->selected_page_id;
00238       DropDownListStringItem *item = NULL;
00239       if (p->title != NULL) {
00240         item = new DropDownListCharStringItem(p->title, p->index, current_page);
00241       } else {
00242         /* No custom title => use a generic page title with page number. */
00243         DropDownListParamStringItem *str_item =
00244             new DropDownListParamStringItem(STR_STORY_BOOK_GENERIC_PAGE_ITEM, p->index, current_page);
00245         str_item->SetParam(0, page_num);
00246         item = str_item;
00247       }
00248 
00249       *list->Append() = item;
00250       page_num++;
00251     }
00252 
00253     /* Check if list is empty. */
00254     if (list->Length() == 0) {
00255       delete list;
00256       list = NULL;
00257     }
00258 
00259     return list;
00260   }
00261 
00265   uint GetAvailablePageContentWidth()
00266   {
00267     return this->GetWidget<NWidgetCore>(WID_SB_PAGE_PANEL)->current_x - WD_FRAMETEXT_LEFT - WD_FRAMERECT_RIGHT;
00268   }
00269 
00277   uint GetHeadHeight(int max_width) const
00278   {
00279     StoryPage *page = this->GetSelPage();
00280     if (page == NULL) return 0;
00281     int height = 0;
00282 
00283     /* Title lines */
00284     height += FONT_HEIGHT_NORMAL; // Date always use exactly one line.
00285     SetDParamStr(0, page->title != NULL ? page->title : this->selected_generic_title);
00286     height += GetStringHeight(STR_STORY_BOOK_TITLE, max_width);
00287 
00288     return height;
00289   }
00290 
00297   SpriteID GetPageElementSprite(const StoryPageElement &pe) const
00298   {
00299     switch (pe.type) {
00300       case SPET_GOAL: {
00301         Goal *g = Goal::Get((GoalID) pe.referenced_id);
00302         if (g == NULL) return SPR_IMG_GOAL_BROKEN_REF;
00303         return g->completed ? SPR_IMG_GOAL_COMPLETED : SPR_IMG_GOAL;
00304       }
00305       case SPET_LOCATION:
00306         return SPR_IMG_VIEW_LOCATION;
00307       default:
00308         NOT_REACHED();
00309     }
00310   }
00311 
00318   uint GetPageElementHeight(const StoryPageElement &pe, int max_width)
00319   {
00320     switch (pe.type) {
00321       case SPET_TEXT:
00322         SetDParamStr(0, pe.text);
00323         return GetStringHeight(STR_BLACK_RAW_STRING, max_width);
00324         break;
00325 
00326       case SPET_GOAL:
00327       case SPET_LOCATION: {
00328         Dimension sprite_dim = GetSpriteSize(GetPageElementSprite(pe));
00329         return sprite_dim.height;
00330         break;
00331       }
00332       default:
00333         NOT_REACHED();
00334     }
00335     return 0;
00336   }
00337 
00343   uint GetContentHeight()
00344   {
00345     StoryPage *page = this->GetSelPage();
00346     if (page == NULL) return 0;
00347     int max_width = GetAvailablePageContentWidth();
00348     uint element_vertical_dist = FONT_HEIGHT_NORMAL;
00349 
00350     /* Head */
00351     uint height = GetHeadHeight(max_width);
00352 
00353     /* Body */
00354     for (const StoryPageElement **iter = this->story_page_elements.Begin(); iter != this->story_page_elements.End(); iter++) {
00355       const StoryPageElement *pe = *iter;
00356       height += element_vertical_dist;
00357       height += GetPageElementHeight(*pe, max_width);
00358     }
00359 
00360     return height;
00361   }
00362 
00374   void DrawActionElement(int &y_offset, int width, int line_height, SpriteID action_sprite, StringID string_id = STR_JUST_RAW_STRING) const
00375   {
00376     Dimension sprite_dim = GetSpriteSize(action_sprite);
00377     uint element_height = max(sprite_dim.height, (uint)line_height);
00378 
00379     uint sprite_top = y_offset + (element_height - sprite_dim.height) / 2;
00380     uint text_top = y_offset + (element_height - line_height) / 2;
00381 
00382     DrawSprite(action_sprite, PAL_NONE, 0, sprite_top);
00383     DrawString(sprite_dim.width + WD_FRAMETEXT_LEFT, width, text_top, string_id, TC_BLACK);
00384 
00385     y_offset += element_height;
00386   }
00387 
00392   void OnPageElementClick(const StoryPageElement& pe)
00393   {
00394     switch (pe.type) {
00395       case SPET_TEXT:
00396         /* Do nothing. */
00397         break;
00398 
00399       case SPET_LOCATION:
00400         if (_ctrl_pressed) {
00401           ShowExtraViewPortWindow((TileIndex)pe.referenced_id);
00402         } else {
00403           ScrollMainWindowToTile((TileIndex)pe.referenced_id);
00404         }
00405         break;
00406 
00407       case SPET_GOAL:
00408         ShowGoalsList((CompanyID)this->window_number);
00409         break;
00410 
00411       default:
00412         NOT_REACHED();
00413     }
00414   }
00415 
00416 public:
00417   StoryBookWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc)
00418   {
00419     this->CreateNestedTree();
00420     this->vscroll = this->GetScrollbar(WID_SB_SCROLLBAR);
00421     this->vscroll->SetStepSize(FONT_HEIGHT_NORMAL);
00422 
00423     /* Initalize page sort. */
00424     this->story_pages.SetSortFuncs(StoryBookWindow::page_sorter_funcs);
00425     this->story_pages.ForceRebuild();
00426     this->BuildStoryPageList();
00427     this->story_page_elements.SetSortFuncs(StoryBookWindow::page_element_sorter_funcs);
00428     /* story_page_elements will get built by SetSelectedPage */
00429 
00430     this->FinishInitNested(window_number);
00431     this->owner = (Owner)this->window_number;
00432 
00433     /* Initialize selected vars. */
00434     this->selected_generic_title[0] = '\0';
00435     this->selected_page_id = INVALID_STORY_PAGE;
00436 
00437     this->OnInvalidateData(-1);
00438   }
00439 
00443   void UpdatePrevNextDisabledState()
00444   {
00445     this->SetWidgetDisabledState(WID_SB_PREV_PAGE, story_pages.Length() == 0 || this->IsFirstPageSelected());
00446     this->SetWidgetDisabledState(WID_SB_NEXT_PAGE, story_pages.Length() == 0 || this->IsLastPageSelected());
00447     this->SetWidgetDirty(WID_SB_PREV_PAGE);
00448     this->SetWidgetDirty(WID_SB_NEXT_PAGE);
00449   }
00450 
00455   void SetSelectedPage(uint16 page_index)
00456   {
00457     if (this->selected_page_id != page_index) {
00458       this->selected_page_id = page_index;
00459       this->RefreshSelectedPage();
00460       this->UpdatePrevNextDisabledState();
00461     }
00462   }
00463 
00464   virtual void SetStringParameters(int widget) const
00465   {
00466     switch (widget) {
00467       case WID_SB_SEL_PAGE: {
00468         StoryPage *page = this->GetSelPage();
00469         SetDParamStr(0, page != NULL && page->title != NULL ? page->title : this->selected_generic_title);
00470         break;
00471       }
00472       case WID_SB_CAPTION:
00473         if (this->window_number == INVALID_COMPANY) {
00474           SetDParam(0, STR_STORY_BOOK_SPECTATOR_CAPTION);
00475         } else {
00476           SetDParam(0, STR_STORY_BOOK_CAPTION);
00477           SetDParam(1, this->window_number);
00478         }
00479         break;
00480     }
00481   }
00482 
00483   virtual void OnPaint()
00484   {
00485     /* Detect if content has changed height. This can happen if a
00486      * multi-line text contains eg. {COMPANY} and that company is
00487      * renamed.
00488      */
00489     if (this->vscroll->GetCount() != this->GetContentHeight()) {
00490       this->vscroll->SetCount(this->GetContentHeight());
00491       this->SetWidgetDirty(WID_SB_SCROLLBAR);
00492       this->SetWidgetDirty(WID_SB_PAGE_PANEL);
00493     }
00494 
00495     this->DrawWidgets();
00496   }
00497 
00498   virtual void DrawWidget(const Rect &r, int widget) const
00499   {
00500     if (widget != WID_SB_PAGE_PANEL) return;
00501 
00502     StoryPage *page = this->GetSelPage();
00503     if (page == NULL) return;
00504 
00505     const int x = r.left + WD_FRAMETEXT_LEFT;
00506     const int y = r.top + WD_FRAMETEXT_TOP;
00507     const int right = r.right - WD_FRAMETEXT_RIGHT;
00508     const int bottom = r.bottom - WD_FRAMETEXT_BOTTOM;
00509 
00510     /* Set up a clipping region for the panel. */
00511     DrawPixelInfo tmp_dpi;
00512     if (!FillDrawPixelInfo(&tmp_dpi, x, y, right - x + 1, bottom - y + 1)) return;
00513 
00514     DrawPixelInfo *old_dpi = _cur_dpi;
00515     _cur_dpi = &tmp_dpi;
00516 
00517     /* Draw content (now coordinates given to Draw** are local to the new clipping region). */
00518     int line_height = FONT_HEIGHT_NORMAL;
00519     int y_offset = - this->vscroll->GetPosition();
00520 
00521     /* Date */
00522     if (page->date != INVALID_DATE) {
00523       SetDParam(0, page->date);
00524       DrawString(0, right - x, y_offset, STR_JUST_DATE_LONG, TC_BLACK);
00525     }
00526     y_offset += line_height;
00527 
00528     /* Title */
00529     SetDParamStr(0, page->title != NULL ? page->title : this->selected_generic_title);
00530     y_offset = DrawStringMultiLine(0, right - x, y_offset, bottom - y, STR_STORY_BOOK_TITLE, TC_BLACK, SA_TOP | SA_HOR_CENTER);
00531 
00532     /* Page elements */
00533     for (const StoryPageElement *const*iter = this->story_page_elements.Begin(); iter != this->story_page_elements.End(); iter++) {
00534       const StoryPageElement *const pe = *iter;
00535       y_offset += line_height; // margin to previous element
00536 
00537       switch (pe->type) {
00538         case SPET_TEXT:
00539           SetDParamStr(0, pe->text);
00540           y_offset = DrawStringMultiLine(0, right - x, y_offset, bottom - y, STR_JUST_RAW_STRING, TC_BLACK, SA_TOP | SA_LEFT);
00541           break;
00542 
00543         case SPET_GOAL: {
00544           Goal *g = Goal::Get((GoalID) pe->referenced_id);
00545           StringID string_id = g == NULL ? STR_STORY_BOOK_INVALID_GOAL_REF : STR_JUST_RAW_STRING;
00546           if (g != NULL) SetDParamStr(0, g->text);
00547           DrawActionElement(y_offset, right - x, line_height, GetPageElementSprite(*pe), string_id);
00548           break;
00549         }
00550 
00551         case SPET_LOCATION:
00552           SetDParamStr(0, pe->text);
00553           DrawActionElement(y_offset, right - x, line_height, GetPageElementSprite(*pe));
00554           break;
00555 
00556         default: NOT_REACHED();
00557       }
00558     }
00559 
00560     /* Restore clipping region. */
00561     _cur_dpi = old_dpi;
00562   }
00563 
00564   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
00565   {
00566     if (widget != WID_SB_SEL_PAGE && widget != WID_SB_PAGE_PANEL) return;
00567 
00568     Dimension d;
00569     d.height= FONT_HEIGHT_NORMAL;
00570     d.width = 0;
00571 
00572     switch(widget) {
00573       case WID_SB_SEL_PAGE: {
00574 
00575         /* Get max title width. */
00576         for (uint16 i = 0; i < this->story_pages.Length(); i++) {
00577           const StoryPage *s = this->story_pages[i];
00578 
00579           if (s->title != NULL) {
00580             SetDParamStr(0, s->title);
00581           } else {
00582             SetDParamStr(0, this->selected_generic_title);
00583           }
00584           Dimension title_d = GetStringBoundingBox(STR_BLACK_RAW_STRING);
00585 
00586           if (title_d.width > d.width) {
00587             d.width = title_d.width;
00588           }
00589         }
00590 
00591         d.width += padding.width;
00592         d.height += padding.height;
00593         *size = maxdim(*size, d);
00594         break;
00595       }
00596 
00597       case WID_SB_PAGE_PANEL: {
00598         d.height *= 5;
00599         d.height += padding.height + WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM;
00600         *size = maxdim(*size, d);
00601         break;
00602       }
00603     }
00604 
00605   }
00606 
00607   virtual void OnResize()
00608   {
00609     this->vscroll->SetCapacityFromWidget(this, WID_SB_PAGE_PANEL, WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM);
00610     this->vscroll->SetCount(this->GetContentHeight());
00611   }
00612 
00613   virtual void OnClick(Point pt, int widget, int click_count)
00614   {
00615     switch (widget) {
00616       case WID_SB_SEL_PAGE: {
00617         DropDownList *list = this->BuildDropDownList();
00618         if (list != NULL) {
00619           /* Get the index of selected page. */
00620           int selected = 0;
00621           for (uint16 i = 0; i < this->story_pages.Length(); i++) {
00622             const StoryPage *p = this->story_pages[i];
00623             if (p->index == this->selected_page_id) break;
00624             selected++;
00625           }
00626 
00627           ShowDropDownList(this, list, selected, widget);
00628         }
00629         break;
00630       }
00631 
00632       case WID_SB_PREV_PAGE:
00633         this->SelectPrevPage();
00634         break;
00635 
00636       case WID_SB_NEXT_PAGE:
00637         this->SelectNextPage();
00638         break;
00639 
00640       case WID_SB_PAGE_PANEL: {
00641         uint clicked_y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SB_PAGE_PANEL, WD_FRAMETEXT_TOP);
00642         uint max_width = GetAvailablePageContentWidth();
00643 
00644         /* Skip head rows. */
00645         uint head_height = this->GetHeadHeight(max_width);
00646         if (clicked_y < head_height) return;
00647 
00648         /* Detect if a page element was clicked. */
00649         uint y = head_height;
00650         uint element_vertical_dist = FONT_HEIGHT_NORMAL;
00651         for (const StoryPageElement *const*iter = this->story_page_elements.Begin(); iter != this->story_page_elements.End(); iter++) {
00652           const StoryPageElement *const pe = *iter;
00653 
00654           y += element_vertical_dist; // margin row
00655 
00656           uint content_height = GetPageElementHeight(*pe, max_width);
00657           if (clicked_y >= y && clicked_y < y + content_height) {
00658             this->OnPageElementClick(*pe);
00659             return;
00660           }
00661 
00662           y += content_height;
00663         }
00664       }
00665     }
00666   }
00667 
00668   virtual void OnDropdownSelect(int widget, int index)
00669   {
00670     if (widget != WID_SB_SEL_PAGE) return;
00671 
00672     /* index (which is set in BuildDropDownList) is the page id. */
00673     this->SetSelectedPage(index);
00674   }
00675 
00683   virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
00684   {
00685     if (!gui_scope) return;
00686 
00687     /* If added/removed page, force rebuild. Sort order never change so just a
00688      * re-sort is never needed.
00689      */
00690     if (data == -1) {
00691       this->story_pages.ForceRebuild();
00692       this->BuildStoryPageList();
00693 
00694       /* Was the last page removed? */
00695       if (this->story_pages.Length() == 0) {
00696         this->selected_generic_title[0] = '\0';
00697       }
00698 
00699       /* Verify page selection. */
00700       if (!_story_page_pool.IsValidID(this->selected_page_id)) {
00701         this->selected_page_id = INVALID_STORY_PAGE;
00702       }
00703       if (this->selected_page_id == INVALID_STORY_PAGE && this->story_pages.Length() > 0) {
00704         /* No page is selected, but there exist at least one available.
00705          * => Select first page.
00706          */
00707         this->SetSelectedPage(this->story_pages[0]->index);
00708       }
00709 
00710       this->SetWidgetDisabledState(WID_SB_SEL_PAGE, this->story_pages.Length() == 0);
00711       this->SetWidgetDirty(WID_SB_SEL_PAGE);
00712       this->UpdatePrevNextDisabledState();
00713     } else if (data >= 0 && this->selected_page_id == data) {
00714       this->RefreshSelectedPage();
00715     }
00716   }
00717 };
00718 
00719 GUIStoryPageList::SortFunction * const StoryBookWindow::page_sorter_funcs[] = {
00720   &PageOrderSorter,
00721 };
00722 
00723 GUIStoryPageElementList::SortFunction * const StoryBookWindow::page_element_sorter_funcs[] = {
00724   &PageElementOrderSorter,
00725 };
00726 
00727 static const NWidgetPart _nested_story_book_widgets[] = {
00728   NWidget(NWID_HORIZONTAL),
00729     NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
00730     NWidget(WWT_CAPTION, COLOUR_BROWN, WID_SB_CAPTION), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00731     NWidget(WWT_SHADEBOX, COLOUR_BROWN),
00732     NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
00733     NWidget(WWT_STICKYBOX, COLOUR_BROWN),
00734   EndContainer(),
00735   NWidget(NWID_HORIZONTAL), SetFill(1, 1),
00736     NWidget(NWID_VERTICAL), SetFill(1, 1),
00737       NWidget(WWT_PANEL, COLOUR_BROWN, WID_SB_PAGE_PANEL), SetResize(1, 1), SetScrollbar(WID_SB_SCROLLBAR), EndContainer(),
00738       NWidget(NWID_HORIZONTAL),
00739         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),
00740         NWidget(NWID_BUTTON_DROPDOWN, COLOUR_BROWN, WID_SB_SEL_PAGE), SetMinimalSize(93, 12), SetFill(1, 0),
00741                             SetDataTip(STR_BLACK_RAW_STRING, STR_STORY_BOOK_SEL_PAGE_TOOLTIP), SetResize(1, 0),
00742         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),
00743       EndContainer(),
00744     EndContainer(),
00745     NWidget(NWID_VERTICAL), SetFill(0, 1),
00746       NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_SB_SCROLLBAR),
00747       NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
00748     EndContainer(),
00749   EndContainer(),
00750 };
00751 
00752 static WindowDesc _story_book_desc(
00753   WDP_CENTER, "view_story", 400, 300,
00754   WC_STORY_BOOK, WC_NONE,
00755   0,
00756   _nested_story_book_widgets, lengthof(_nested_story_book_widgets)
00757 );
00758 
00759 void ShowStoryBook(CompanyID company, uint16 page_id)
00760 {
00761   if (!Company::IsValidID(company)) company = (CompanyID)INVALID_COMPANY;
00762 
00763   StoryBookWindow *w = AllocateWindowDescFront<StoryBookWindow>(&_story_book_desc, company);
00764   if (page_id != INVALID_STORY_PAGE) {
00765     if (w == NULL) w = (StoryBookWindow *)FindWindowById(WC_STORY_BOOK, company);
00766     w->SetSelectedPage(page_id);
00767   }
00768 }