diff --git a/bin/baseset/openttd.grf b/bin/baseset/openttd.grf index afee09e4fd..86b70789f0 100644 Binary files a/bin/baseset/openttd.grf and b/bin/baseset/openttd.grf differ diff --git a/bin/baseset/orig_dos.obg b/bin/baseset/orig_dos.obg index 593257fb64..16cf83a7ff 100644 --- a/bin/baseset/orig_dos.obg +++ b/bin/baseset/orig_dos.obg @@ -64,7 +64,7 @@ TRGI.GRF = da6a6c9dcc451eec88d79211437b76a8 TRGC.GRF = ed446637e034104c5559b32c18afe78d TRGH.GRF = ee6616fb0e6ef6b24892c58c93d86fc9 TRGT.GRF = e30e8a398ae86c03dc534a8ac7dfb3b6 -OPENTTD.GRF = 9285f720c9c6805b4adcfcbf869a7deb +OPENTTD.GRF = 63d40d4bc6cfb3546cab72d94614e6cd [origin] default = You can find it on your Transport Tycoon Deluxe CD-ROM. diff --git a/bin/baseset/orig_dos_de.obg b/bin/baseset/orig_dos_de.obg index aa8112d5fb..a2132dde9e 100644 --- a/bin/baseset/orig_dos_de.obg +++ b/bin/baseset/orig_dos_de.obg @@ -64,7 +64,7 @@ TRGI.GRF = da6a6c9dcc451eec88d79211437b76a8 TRGC.GRF = ed446637e034104c5559b32c18afe78d TRGH.GRF = ee6616fb0e6ef6b24892c58c93d86fc9 TRGT.GRF = fcde1d7e8a74197d72a62695884b909e -OPENTTD.GRF = 9285f720c9c6805b4adcfcbf869a7deb +OPENTTD.GRF = 63d40d4bc6cfb3546cab72d94614e6cd [origin] default = You can find it on your Transport Tycoon Deluxe CD-ROM. diff --git a/bin/baseset/orig_win.obg b/bin/baseset/orig_win.obg index 8d50bbca4c..bc3411ba83 100644 --- a/bin/baseset/orig_win.obg +++ b/bin/baseset/orig_win.obg @@ -64,7 +64,7 @@ TRGIR.GRF = 0c2484ff6be49fc63a83be6ab5c38f32 TRGCR.GRF = 3668f410c761a050b5e7095a2b14879b TRGHR.GRF = 06bf2b7a31766f048baac2ebe43457b1 TRGTR.GRF = de53650517fe661ceaa3138c6edb0eb8 -OPENTTD.GRF = 9285f720c9c6805b4adcfcbf869a7deb +OPENTTD.GRF = 63d40d4bc6cfb3546cab72d94614e6cd [origin] default = You can find it on your Transport Tycoon Deluxe CD-ROM. diff --git a/media/extra_grf/openttdgui.nfo b/media/extra_grf/openttdgui.nfo index 33ad4cb8b4..1d96de8558 100644 --- a/media/extra_grf/openttdgui.nfo +++ b/media/extra_grf/openttdgui.nfo @@ -7,7 +7,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 . // -1 * 0 0C "OpenTTD GUI graphics" - -1 * 3 05 15 \b 169 // OPENTTD_SPRITE_COUNT + -1 * 3 05 15 \b 174 // OPENTTD_SPRITE_COUNT -1 sprites/openttdgui.png 8bpp 66 8 64 31 -31 7 normal -1 sprites/openttdgui.png 8bpp 146 8 64 31 -31 7 normal -1 sprites/openttdgui.png 8bpp 226 8 64 31 -31 7 normal @@ -177,3 +177,8 @@ -1 sprites/openttdgui.png 8bpp 312 440 14 10 0 0 normal -1 sprites/openttdgui.png 8bpp 328 440 14 10 0 0 normal -1 sprites/openttdgui.png 8bpp 348 440 8 8 0 0 normal + -1 sprites/openttdgui.png 8bpp 362 440 20 20 0 0 normal + -1 sprites/openttdgui.png 8bpp 388 440 20 20 0 0 normal + -1 sprites/openttdgui.png 8bpp 414 440 20 20 0 0 normal + -1 sprites/openttdgui.png 8bpp 440 440 20 20 0 0 normal + -1 sprites/openttdgui.png 8bpp 466 440 20 20 0 0 normal diff --git a/media/extra_grf/openttdgui.png b/media/extra_grf/openttdgui.png index f03033d296..6f011b654d 100644 Binary files a/media/extra_grf/openttdgui.png and b/media/extra_grf/openttdgui.png differ diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj index 1a772c6e8f..41aeeabacc 100644 --- a/projects/openttd_vs100.vcxproj +++ b/projects/openttd_vs100.vcxproj @@ -705,6 +705,7 @@ + @@ -763,6 +764,7 @@ + diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters index c521cc6d82..f2900c846f 100644 --- a/projects/openttd_vs100.vcxproj.filters +++ b/projects/openttd_vs100.vcxproj.filters @@ -1344,6 +1344,9 @@ GUI Source Code + + GUI Source Code + GUI Source Code @@ -1518,6 +1521,9 @@ Widgets + + Widgets + Widgets diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj index d6d806212d..5c866b99dd 100644 --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -2102,6 +2102,10 @@ RelativePath=".\..\src\statusbar_gui.cpp" > + + @@ -2338,6 +2342,10 @@ RelativePath=".\..\src\widgets\statusbar_widget.h" > + + diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj index 8219894e7a..96120ed473 100644 --- a/projects/openttd_vs90.vcproj +++ b/projects/openttd_vs90.vcproj @@ -2099,6 +2099,10 @@ RelativePath=".\..\src\statusbar_gui.cpp" > + + @@ -2335,6 +2339,10 @@ RelativePath=".\..\src\widgets\statusbar_widget.h" > + + diff --git a/source.list b/source.list index 1a72fbd8ba..be324f335f 100644 --- a/source.list +++ b/source.list @@ -457,6 +457,7 @@ signs_gui.cpp smallmap_gui.cpp station_gui.cpp statusbar_gui.cpp +story_gui.cpp subsidy_gui.cpp terraform_gui.cpp textfile_gui.cpp @@ -517,6 +518,7 @@ widgets/sign_widget.h widgets/smallmap_widget.h widgets/station_widget.h widgets/statusbar_widget.h +widgets/story_widget.h widgets/subsidy_widget.h widgets/terraform_widget.h widgets/timetable_widget.h diff --git a/src/gui.h b/src/gui.h index 8b31c6c984..79c33e0b37 100644 --- a/src/gui.h +++ b/src/gui.h @@ -51,6 +51,7 @@ void ShowIndustryCargoesWindow(); void ShowSubsidiesList(); void ShowGoalsList(); void ShowGoalQuestion(uint16 id, byte type, uint32 button_mask, const char *question); +void ShowStoryBook(); void ShowEstimatedCostOrIncome(Money cost, int x, int y); diff --git a/src/lang/english.txt b/src/lang/english.txt index 6e5b8eeb14..9fef3802d0 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -399,6 +399,7 @@ STR_GRAPH_MENU_CARGO_PAYMENT_RATES :Cargo payment r ############ range for company league menu starts STR_GRAPH_MENU_GOAL :Current goals +STR_GRAPH_MENU_STORY_BOOK :Story book STR_GRAPH_MENU_COMPANY_LEAGUE_TABLE :Company league table STR_GRAPH_MENU_DETAILED_PERFORMANCE_RATING :Detailed performance rating STR_GRAPH_MENU_HIGHSCORE :Highscore table @@ -2947,6 +2948,16 @@ STR_SUBSIDIES_SUBSIDISED_TITLE :{BLACK}Services STR_SUBSIDIES_SUBSIDISED_FROM_TO :{ORANGE}{STRING} from {STRING2} to {STRING2}{YELLOW} ({COMPANY}{YELLOW}, until {DATE_SHORT}) STR_SUBSIDIES_TOOLTIP_CLICK_ON_SERVICE_TO_CENTER :{BLACK}Click on service to centre main view on industry/town. Ctrl+Click opens a new viewport on industry/town location +# Story book window +STR_STORY_BOOK_CAPTION :{WHITE}Story Book +STR_STORY_BOOK_TITLE :{YELLOW}{RAW_STRING} +STR_STORY_BOOK_GENERIC_PAGE_ITEM :Page {NUM} +STR_STORY_BOOK_SEL_PAGE_TOOLTIP :{BLACK}Jump to a specific page by selecting it in this drop down list. +STR_STORY_BOOK_PREV_PAGE :{BLACK}Previous +STR_STORY_BOOK_PREV_PAGE_TOOLTIP :{BLACK}Go to previous page +STR_STORY_BOOK_NEXT_PAGE :{BLACK}Next +STR_STORY_BOOK_NEXT_PAGE_TOOLTIP :{BLACK}Go to next page + # Station list window STR_STATION_LIST_TOOLTIP :{BLACK}Station names - click on name to centre main view on station. Ctrl+Click opens a new viewport on station location STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE :{BLACK}Hold Ctrl to select more than one item diff --git a/src/script/api/game/game_window.hpp.sq b/src/script/api/game/game_window.hpp.sq index 01811a2125..73b4d4bac9 100644 --- a/src/script/api/game/game_window.hpp.sq +++ b/src/script/api/game/game_window.hpp.sq @@ -76,6 +76,7 @@ void SQGSWindow_Register(Squirrel *engine) SQGSWindow.DefSQConst(engine, ScriptWindow::WC_SIGN_LIST, "WC_SIGN_LIST"); SQGSWindow.DefSQConst(engine, ScriptWindow::WC_AI_LIST, "WC_AI_LIST"); SQGSWindow.DefSQConst(engine, ScriptWindow::WC_GOALS_LIST, "WC_GOALS_LIST"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WC_STORY_BOOK, "WC_STORY_BOOK"); SQGSWindow.DefSQConst(engine, ScriptWindow::WC_STATION_LIST, "WC_STATION_LIST"); SQGSWindow.DefSQConst(engine, ScriptWindow::WC_TRAINS_LIST, "WC_TRAINS_LIST"); SQGSWindow.DefSQConst(engine, ScriptWindow::WC_ROADVEH_LIST, "WC_ROADVEH_LIST"); @@ -1084,6 +1085,12 @@ void SQGSWindow_Register(Squirrel *engine) SQGSWindow.DefSQConst(engine, ScriptWindow::WID_S_LEFT, "WID_S_LEFT"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_S_MIDDLE, "WID_S_MIDDLE"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_S_RIGHT, "WID_S_RIGHT"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_SB_CAPTION, "WID_SB_CAPTION"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_SB_SEL_PAGE, "WID_SB_SEL_PAGE"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_SB_PAGE_PANEL, "WID_SB_PAGE_PANEL"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_SB_SCROLLBAR, "WID_SB_SCROLLBAR"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_SB_PREV_PAGE, "WID_SB_PREV_PAGE"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_SB_NEXT_PAGE, "WID_SB_NEXT_PAGE"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_SUL_PANEL, "WID_SUL_PANEL"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_SUL_SCROLLBAR, "WID_SUL_SCROLLBAR"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_TT_SHOW_PLACE_OBJECT, "WID_TT_SHOW_PLACE_OBJECT"); diff --git a/src/script/api/script_window.hpp b/src/script/api/script_window.hpp index 1fae2bfae8..e9e10e3ed5 100644 --- a/src/script/api/script_window.hpp +++ b/src/script/api/script_window.hpp @@ -69,6 +69,7 @@ #include "../../widgets/viewport_widget.h" #include "../../widgets/waypoint_widget.h" #include "../../widgets/link_graph_legend_widget.h" +#include "../../widgets/story_widget.h" /** * Class that handles window interaction. A Window in OpenTTD has two imporant @@ -366,6 +367,11 @@ public: */ WC_GOALS_LIST = ::WC_GOALS_LIST, + /** + * Story book; %Window numbers: + * - CompanyID = #StoryBookWidgets + */ + WC_STORY_BOOK = ::WC_STORY_BOOK, /** * Station list; %Window numbers: @@ -2241,6 +2247,17 @@ public: WID_S_RIGHT = ::WID_S_RIGHT, ///< Right part; bank balance. }; + /* automatically generated from ../../widgets/story_widget.h */ + /** Widgets of the #GoalListWindow class. */ + enum StoryBookWidgets { + WID_SB_CAPTION = ::WID_SB_CAPTION, ///< Caption of the window. + WID_SB_SEL_PAGE = ::WID_SB_SEL_PAGE, ///< Page selector. + WID_SB_PAGE_PANEL = ::WID_SB_PAGE_PANEL, ///< Page body. + WID_SB_SCROLLBAR = ::WID_SB_SCROLLBAR, ///< Scrollbar of the goal list. + WID_SB_PREV_PAGE = ::WID_SB_PREV_PAGE, ///< Prev button. + WID_SB_NEXT_PAGE = ::WID_SB_NEXT_PAGE, ///< Next button. + }; + /* automatically generated from ../../widgets/subsidy_widget.h */ /** Widgets of the #SubsidyListWindow class. */ enum SubsidyListWidgets { diff --git a/src/script/api/template/template_window.hpp.sq b/src/script/api/template/template_window.hpp.sq index 238c403c2e..9f01e42d5a 100644 --- a/src/script/api/template/template_window.hpp.sq +++ b/src/script/api/template/template_window.hpp.sq @@ -209,6 +209,8 @@ namespace SQConvert { template <> inline int Return(HSQUIRRELVM vm, ScriptWindow::JoinStationWidgets res) { sq_pushinteger(vm, (int32)res); return 1; } template <> inline ScriptWindow::StatusbarWidgets GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptWindow::StatusbarWidgets)tmp; } template <> inline int Return(HSQUIRRELVM vm, ScriptWindow::StatusbarWidgets res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> inline ScriptWindow::StoryBookWidgets GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptWindow::StoryBookWidgets)tmp; } + template <> inline int Return(HSQUIRRELVM vm, ScriptWindow::StoryBookWidgets res) { sq_pushinteger(vm, (int32)res); return 1; } template <> inline ScriptWindow::SubsidyListWidgets GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptWindow::SubsidyListWidgets)tmp; } template <> inline int Return(HSQUIRRELVM vm, ScriptWindow::SubsidyListWidgets res) { sq_pushinteger(vm, (int32)res); return 1; } template <> inline ScriptWindow::TerraformToolbarWidgets GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptWindow::TerraformToolbarWidgets)tmp; } diff --git a/src/story.cpp b/src/story.cpp index 3574cf5252..e492ded4da 100644 --- a/src/story.cpp +++ b/src/story.cpp @@ -20,6 +20,7 @@ #include "tile_map.h" #include "goal_type.h" #include "goal_base.h" +#include "window_func.h" StoryPageElementID _new_story_page_element_id; @@ -123,6 +124,8 @@ CommandCost CmdCreateStoryPage(TileIndex tile, DoCommandFlag flags, uint32 p1, u s->title = strdup(text); } + InvalidateWindowData(WC_STORY_BOOK, -1); + _new_story_page_id = s->index; _story_page_next_sort_value++; } @@ -172,6 +175,8 @@ CommandCost CmdCreateStoryPageElement(TileIndex tile, DoCommandFlag flags, uint3 pe->page = page_id; UpdateElement(*pe, tile, p2, text); + InvalidateWindowData(WC_STORY_BOOK, page_id); + _new_story_page_element_id = pe->index; _story_page_element_next_sort_value++; } @@ -205,6 +210,7 @@ CommandCost CmdUpdateStoryPageElement(TileIndex tile, DoCommandFlag flags, uint3 if (flags & DC_EXEC) { UpdateElement(*pe, tile, p2, text); + InvalidateWindowData(WC_STORY_BOOK, pe->page); } return CommandCost(); @@ -233,6 +239,8 @@ CommandCost CmdSetStoryPageTitle(TileIndex tile, DoCommandFlag flags, uint32 p1, } else { p->title = strdup(text); } + + InvalidateWindowData(WC_STORY_BOOK, page_id); } return CommandCost(); @@ -264,6 +272,8 @@ CommandCost CmdRemoveStoryPage(TileIndex tile, DoCommandFlag flags, uint32 p1, u } delete p; + + InvalidateWindowData(WC_STORY_BOOK, -1); } return CommandCost(); diff --git a/src/story_gui.cpp b/src/story_gui.cpp new file mode 100644 index 0000000000..62ec3606a8 --- /dev/null +++ b/src/story_gui.cpp @@ -0,0 +1,731 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +/** @file goal_gui.cpp GUI for goals. */ + +#include "stdafx.h" +#include "window_gui.h" +#include "strings_func.h" +#include "date_func.h" +#include "gui.h" +#include "story_base.h" +#include "core/geometry_func.hpp" +#include "company_func.h" +#include "command_func.h" +#include "widgets/dropdown_type.h" +#include "widgets/dropdown_func.h" +#include "sortlist_type.h" +#include "goal_base.h" +#include "viewport_func.h" + +#include "widgets/story_widget.h" + +#include "table/strings.h" +#include "table/sprites.h" + +typedef GUIList GUIStoryPageList; +typedef GUIList GUIStoryPageElementList; + +struct StoryBookWindow : Window { +protected: + Scrollbar *vscroll; ///< Scrollbar of the page text. + + GUIStoryPageList story_pages; ///< Sorted list of pages. + GUIStoryPageElementList story_page_elements; ///< Sorted list of page elements that belong to the current page. + StoryPageID selected_page_id; ///< Pool index of selected page. + 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. + + static GUIStoryPageList::SortFunction * const page_sorter_funcs[]; + static GUIStoryPageElementList::SortFunction * const page_element_sorter_funcs[]; + + /** (Re)Build story page list. */ + void BuildStoryPageList() + { + if (this->story_pages.NeedRebuild()) { + this->story_pages.Clear(); + + const StoryPage *p; + FOR_ALL_STORY_PAGES(p) { + if (this->IsPageAvailable(p)) { + *this->story_pages.Append() = p; + } + } + + this->story_pages.Compact(); + this->story_pages.RebuildDone(); + } + + this->story_pages.Sort(); + } + + /** Sort story pages by order value. */ + static int CDECL PageOrderSorter(const StoryPage * const *a, const StoryPage * const *b) + { + return (*a)->sort_value - (*b)->sort_value; + } + + /** (Re)Build story page element list. */ + void BuildStoryPageElementList() + { + if (this->story_page_elements.NeedRebuild()) { + this->story_page_elements.Clear(); + + const StoryPage *p = GetSelPage(); + if (p != NULL) { + const StoryPageElement *pe; + FOR_ALL_STORY_PAGE_ELEMENTS(pe) { + if (pe->page == p->index) { + *this->story_page_elements.Append() = pe; + } + } + } + + this->story_page_elements.Compact(); + this->story_page_elements.RebuildDone(); + } + + this->story_page_elements.Sort(); + } + + /** Sort story page elements by order value. */ + static int CDECL PageElementOrderSorter(const StoryPageElement * const *a, const StoryPageElement * const *b) + { + return (*a)->sort_value - (*b)->sort_value; + } + + /* + * Checks if a given page should be visible in the story book. + * @param page The page to check. + * @return True if the page should be visible, otherwise false. + */ + bool IsPageAvailable(const StoryPage *page) const + { + return page->company == INVALID_COMPANY || page->company == _local_company; + } + + /** + * Get instance of selected page. + * @return Instance of selected page or NULL if no page is selected. + */ + StoryPage *GetSelPage() const + { + if (!_story_page_pool.IsValidID(selected_page_id)) return NULL; + return _story_page_pool.Get(selected_page_id); + } + + /** + * Get the page number of selected page. + * @return Number of available pages before to the selected one, or -1 if no page is selected. + */ + int GetSelPageNum() const + { + int page_number = 0; + for (const StoryPage *const*iter = this->story_pages.Begin(); iter != this->story_pages.End(); iter++) { + const StoryPage *p = *iter; + if (p->index == this->selected_page_id) { + return page_number; + } + page_number++; + } + return -1; + } + + /** + * Check if the selected page is also the first available page. + */ + bool IsFirstPageSelected() + { + /* Verify that the selected page exist. */ + if (!_story_page_pool.IsValidID(this->selected_page_id)) return false; + + return (*this->story_pages.Begin())->index == this->selected_page_id; + } + + /** + * Check if the selected page is also the last available page. + */ + bool IsLastPageSelected() + { + /* Verify that the selected page exist. */ + if (!_story_page_pool.IsValidID(this->selected_page_id)) return false; + + if (this->story_pages.Length() <= 1) return true; + const StoryPage *last = *(this->story_pages.End() - 1); + return last->index == this->selected_page_id; + } + + /** + * Updates the content of selected page. + */ + void RefreshSelectedPage() + { + /* Generate generic title if selected page have no custom title. */ + StoryPage *page = this->GetSelPage(); + if (page != NULL && page->title == NULL) { + SetDParam(0, GetSelPageNum() + 1); + GetString(selected_generic_title, STR_STORY_BOOK_GENERIC_PAGE_ITEM, lastof(selected_generic_title)); + } + + this->story_page_elements.ForceRebuild(); + this->BuildStoryPageElementList(); + + this->vscroll->SetCount(this->CountLines()); + this->SetWidgetDirty(WID_SB_SCROLLBAR); + this->SetWidgetDirty(WID_SB_SEL_PAGE); + this->SetWidgetDirty(WID_SB_PAGE_PANEL); + } + + /** + * Selects the previous available page before the currently selected page. + */ + void SelectPrevPage() + { + if (!_story_page_pool.IsValidID(this->selected_page_id)) return; + + /* Find the last available page which is previous to the current selected page. */ + const StoryPage *last_available; + last_available = NULL; + for (const StoryPage *const*iter = this->story_pages.Begin(); iter != this->story_pages.End(); iter++) { + const StoryPage *p = *iter; + if (p->index == this->selected_page_id) { + if (last_available == NULL) return; // No previous page available. + this->SetSelectedPage(last_available->index); + return; + } + last_available = p; + } + } + + /** + * Selects the next available page after the currently selected page. + */ + void SelectNextPage() + { + if (!_story_page_pool.IsValidID(this->selected_page_id)) return; + + /* Find selected page. */ + for (const StoryPage *const*iter = this->story_pages.Begin(); iter != this->story_pages.End(); iter++) { + const StoryPage *p = *iter; + if (p->index == this->selected_page_id) { + /* Select the page after selected page. */ + iter++; + if (iter != this->story_pages.End()) { + this->SetSelectedPage((*iter)->index); + } + return; + } + } + } + + /** + * Builds the page selector drop down list. + */ + DropDownList *BuildDropDownList() const + { + DropDownList *list = new DropDownList(); + uint16 page_num = 1; + for (const StoryPage *const*iter = this->story_pages.Begin(); iter != this->story_pages.End(); iter++) { + const StoryPage *p = *iter; + char *title = p->title; + bool current_page = p->index == this->selected_page_id; + DropDownListStringItem *item = NULL; + if (p->title != NULL) { + item = new DropDownListCharStringItem(p->title, p->index, current_page); + } else { + /* No custom title => use a generic page title with page number. */ + DropDownListParamStringItem *str_item = + new DropDownListParamStringItem(STR_STORY_BOOK_GENERIC_PAGE_ITEM, p->index, current_page); + str_item->SetParam(0, page_num); + item = str_item; + } + + list->push_back(item); + page_num++; + } + + /* Check if list is empty. */ + if (list->size() == 0) { + free(list); + list = NULL; + } + + return list; + } + + /** + * Get the width available for displaying content on the page panel. + */ + uint GetAvailablePageContentWidth() + { + return this->GetWidget(WID_SB_PAGE_PANEL)->current_x - WD_FRAMETEXT_LEFT - WD_FRAMERECT_RIGHT; + } + + /** + * Counts how many lines that are used by Date and Title + * (excluding marginal after Title, as each body element has + * an empty row before the elment). + * @param max_width Available width to display content. + * @return the number of lines. + */ + uint CountHeadLines(int max_width) + { + StoryPage *page = this->GetSelPage(); + if (page == NULL) return 0; + int num_lines = 0; + + /* Title lines */ + num_lines += 1; // Date always use exactly one line. + SetDParamStr(0, page->title != NULL ? page->title : this->selected_generic_title); + num_lines += GetStringLineCount(STR_STORY_BOOK_TITLE, max_width); + + return num_lines; + } + + /** + * Decides which sprite to display for a given page element. + * @param pe The page element. + * @return The SpriteID of the sprite to display. + * @pre pe.type must be SPET_GOAL or SPET_LOCATION. + */ + SpriteID GetPageElementSprite(const StoryPageElement &pe) const + { + switch (pe.type) { + case SPET_GOAL: { + Goal *g = Goal::Get((GoalID) pe.referenced_id); + if (g == NULL) return SPR_IMG_GOAL_BROKEN_REF; + return g->completed ? SPR_IMG_GOAL_COMPLETED : SPR_IMG_GOAL; + } + case SPET_LOCATION: + return SPR_IMG_VIEW_LOCATION; + default: + NOT_REACHED(); + } + } + + /** + * Count the number of lines used by a given page element. + * @param pe The story page element. + * @param max_width Available width to display content. + * @return the number of lines. + */ + uint CountPageElementLines(const StoryPageElement &pe, int max_width) + { + switch (pe.type) { + case SPET_TEXT: + SetDParamStr(0, pe.text); + return GetStringLineCount(STR_BLACK_RAW_STRING, max_width); + break; + + case SPET_GOAL: + case SPET_LOCATION: { + Dimension sprite_dim = GetSpriteSize(GetPageElementSprite(pe)); + int line_height = GetStringHeight(STR_JUST_NOTHING, INT_MAX); + if (line_height == 0) return 1; + return max((uint)1, sprite_dim.height / (uint)line_height); + break; + } + default: + NOT_REACHED(); + } + } + + /** + * Count the number of lines in this window. + * @return the number of lines. + */ + uint CountLines() + { + StoryPage *page = this->GetSelPage(); + if (page == NULL) return 0; + int max_width = GetAvailablePageContentWidth(); + + /* Head lines */ + int num_lines = CountHeadLines(max_width); + + /* Body lines */ + for (const StoryPageElement **iter = this->story_page_elements.Begin(); iter != this->story_page_elements.End(); iter++) { + const StoryPageElement *pe = *iter; + num_lines += 1; // For the space between previous element and current element. + + num_lines += CountPageElementLines(*pe, max_width); + } + + return num_lines; + } + + /** + * Draws a page element that is composed of a sprite to the left and a single line of + * text after that. These page elements are generally clickable and are thus called + * action elements. + * @param y_offset Current y_offset which will get updated when this method has completed its drawing. + * @param width Width of the region available for drawing. + * @param line_height Height of one line of text. + * @param action_sprite The sprite to draw. + * @return the number of lines. + */ + void DrawActionElement(int &y_offset, int width, int line_height, SpriteID action_sprite) const + { + Dimension sprite_dim = GetSpriteSize(action_sprite); + uint element_height = max((uint)1, sprite_dim.height / (uint)line_height) * line_height; + + uint sprite_top = y_offset + (element_height - sprite_dim.height) / 2; + uint text_top = y_offset + (element_height - line_height) / 2; + + DrawSprite(action_sprite, PAL_NONE, 0, y_offset); + DrawString(sprite_dim.width + WD_FRAMETEXT_LEFT, width, text_top, STR_JUST_RAW_STRING, TC_BLACK); + + y_offset += element_height; + } + + /** + * Internal event handler for when a page element is clicked. + * @param pe The clicked page element. + */ + void OnPageElementClick(const StoryPageElement& pe) + { + switch (pe.type) { + case SPET_TEXT: + /* Do nothing. */ + break; + + case SPET_LOCATION: + if (_ctrl_pressed) { + ShowExtraViewPortWindow((TileIndex)pe.referenced_id); + } else { + ScrollMainWindowToTile((TileIndex)pe.referenced_id); + } + break; + + case SPET_GOAL: + ShowGoalsList(); + break; + + default: + NOT_REACHED(); + } + } + +public: + StoryBookWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc) + { + this->CreateNestedTree(); + this->vscroll = this->GetScrollbar(WID_SB_SCROLLBAR); + + /* Initalize page sort. */ + this->story_pages.SetSortFuncs(StoryBookWindow::page_sorter_funcs); + this->story_pages.ForceRebuild(); + this->BuildStoryPageList(); + this->story_page_elements.SetSortFuncs(StoryBookWindow::page_element_sorter_funcs); + /* story_page_elements will get built by SetSelectedPage */ + + this->FinishInitNested(window_number); + + /* Initialize selected vars. */ + this->selected_generic_title[0] = '\0'; + this->selected_page_id = INVALID_STORY_PAGE; + + this->OnInvalidateData(-1); + } + + /** + * Updates the disabled state of the prev/next buttons. + */ + void UpdatePrevNextDisabledState() + { + this->SetWidgetDisabledState(WID_SB_PREV_PAGE, story_pages.Length() == 0 || this->IsFirstPageSelected()); + this->SetWidgetDisabledState(WID_SB_NEXT_PAGE, story_pages.Length() == 0 || this->IsLastPageSelected()); + this->SetWidgetDirty(WID_SB_PREV_PAGE); + this->SetWidgetDirty(WID_SB_NEXT_PAGE); + } + + /** + * Sets the selected page. + * @param page_index pool index of the page to select. + */ + void SetSelectedPage(uint16 page_index) + { + if (this->selected_page_id != page_index) { + this->selected_page_id = page_index; + this->RefreshSelectedPage(); + this->UpdatePrevNextDisabledState(); + } + } + + virtual void SetStringParameters(int widget) const + { + if (widget != WID_SB_SEL_PAGE) return; + + StoryPage *page = this->GetSelPage(); + SetDParamStr(0, page != NULL && page->title != NULL ? page->title : this->selected_generic_title); + } + + virtual void DrawWidget(const Rect &r, int widget) const + { + if (widget != WID_SB_PAGE_PANEL) return; + + StoryPage *page = this->GetSelPage(); + if (page == NULL) return; + + const int x = r.left + WD_FRAMETEXT_LEFT; + const int y = r.top + WD_FRAMETEXT_TOP; + const int right = r.right - WD_FRAMETEXT_RIGHT; + const int bottom = r.bottom - WD_FRAMETEXT_BOTTOM; + + /* Set up a clipping region for the panel. */ + DrawPixelInfo tmp_dpi; + if (!FillDrawPixelInfo(&tmp_dpi, x, y, right - x + 1, r.bottom - y + 1)) return; + + DrawPixelInfo *old_dpi = _cur_dpi; + _cur_dpi = &tmp_dpi; + + /* Draw content (now coordinates given to Draw** are local to the new clipping region). */ + int line_height = GetStringHeight(STR_JUST_NOTHING, INT_MAX); + int y_offset = - this->vscroll->GetPosition() * line_height; + + /* Date */ + SetDParam(0, page->date); + DrawString(0, right - x, y_offset, STR_JUST_DATE_LONG, TC_BLACK); + y_offset += line_height; + + /* Title */ + SetDParamStr(0, page->title != NULL ? page->title : this->selected_generic_title); + y_offset = DrawStringMultiLine(0, right - x, y_offset, bottom - y, STR_STORY_BOOK_TITLE, TC_BLACK, SA_TOP | SA_HOR_CENTER); + + /* Page elements */ + for (const StoryPageElement *const*iter = this->story_page_elements.Begin(); iter != this->story_page_elements.End(); iter++) { + const StoryPageElement *const pe = *iter; + y_offset += line_height; // margin to previous element + + switch (pe->type) { + case SPET_TEXT: + SetDParamStr(0, pe->text); + y_offset = DrawStringMultiLine(0, right - x, y_offset, bottom - y, STR_JUST_RAW_STRING, TC_BLACK, SA_TOP | SA_LEFT); + break; + + case SPET_GOAL: { + Goal *g = Goal::Get((GoalID) pe->referenced_id); + if (g != NULL) { + SetDParamStr(0, g->text); + DrawActionElement(y_offset, right - x, line_height, GetPageElementSprite(*pe)); + } else { + y_offset += line_height; + } + break; + } + + case SPET_LOCATION: + SetDParamStr(0, pe->text); + DrawActionElement(y_offset, right - x, line_height, GetPageElementSprite(*pe)); + break; + } + } + + /* Restore clipping region. */ + _cur_dpi = old_dpi; + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) + { + if (widget != WID_SB_SEL_PAGE && widget != WID_SB_PAGE_PANEL) return; + + Dimension d = GetStringBoundingBox(STR_JUST_NOTHING); + d.width = 0; + + switch(widget) { + case WID_SB_SEL_PAGE: { + + /* Get max title width. */ + for (uint16 i = 0; i < this->story_pages.Length(); i++) { + const StoryPage *s = this->story_pages[i]; + + if (s->title != NULL) { + SetDParamStr(0, s->title); + } else { + SetDParamStr(0, this->selected_generic_title); + } + Dimension title_d = GetStringBoundingBox(STR_BLACK_RAW_STRING); + + if (title_d.width > d.width) { + d.width = title_d.width; + } + } + + d.width += padding.width + WD_DROPDOWNTEXT_LEFT + WD_DROPDOWNTEXT_RIGHT; + d.height += padding.height + WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM; + *size = maxdim(*size, d); + break; + } + + case WID_SB_PAGE_PANEL: { + resize->height = d.height; + + d.height *= 5; + d.height += padding.height + WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM; + *size = maxdim(*size, d); + break; + } + } + + } + + virtual void OnResize() + { + this->vscroll->SetCapacityFromWidget(this, WID_SB_PAGE_PANEL, WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM); + this->vscroll->SetCount(this->CountLines()); + } + + virtual void OnClick(Point pt, int widget, int click_count) + { + switch (widget) { + case WID_SB_SEL_PAGE: { + DropDownList *list = this->BuildDropDownList(); + if (list != NULL) { + /* Get the index of selected page. */ + int selected = 0; + for (uint16 i = 0; i < this->story_pages.Length(); i++) { + const StoryPage *p = this->story_pages[i]; + if (p->index == this->selected_page_id) break; + selected++; + } + + ShowDropDownList(this, list, selected, widget); + } + break; + } + + case WID_SB_PREV_PAGE: + this->SelectPrevPage(); + break; + + case WID_SB_NEXT_PAGE: + this->SelectNextPage(); + break; + + case WID_SB_PAGE_PANEL: { + uint clicked_row = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SB_PAGE_PANEL, WD_FRAMETEXT_TOP); + uint max_width = GetAvailablePageContentWidth(); + + /* Skip head rows. */ + uint n_head_rows = this->CountHeadLines(max_width); + if (clicked_row < n_head_rows) return; + + /* Detect if a page element was clicked. */ + uint row = n_head_rows; + for (const StoryPageElement *const*iter = this->story_page_elements.Begin(); iter != this->story_page_elements.End(); iter++) { + const StoryPageElement *const pe = *iter; + + row += 1; // margin row + + uint content_rows = CountPageElementLines(*pe, max_width); + if (clicked_row >= row && clicked_row < row + content_rows) { + this->OnPageElementClick(*pe); + return; + } + + row += content_rows; + } + } + } + } + + virtual void OnDropdownSelect(int widget, int index) + { + if (widget != WID_SB_SEL_PAGE) return; + + /* index (which is set in BuildDropDownList) is the page id. */ + this->SetSelectedPage(index); + } + + /** + * Some data on this window has become invalid. + * @param data Information about the changed data. + * -1 Rebuild page list and refresh current page; + * >= 0 Id of the page that needs to be refreshed. If it is not the current page, nothing happens. + * @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. + */ + virtual void OnInvalidateData(int data = 0, bool gui_scope = true) + { + if (!gui_scope) return; + + /* If added/removed page, force rebuild. Sort order never change so just a + * re-sort is never needed. + */ + if (data == -1) { + this->story_pages.ForceRebuild(); + this->BuildStoryPageList(); + + /* Was the last page removed? */ + if (this->story_pages.Length() == 0) { + this->selected_generic_title[0] = '\0'; + } + + /* Verify page selection. */ + if (!_story_page_pool.IsValidID(this->selected_page_id)) { + this->selected_page_id = INVALID_STORY_PAGE; + } + if (this->selected_page_id == INVALID_STORY_PAGE && this->story_pages.Length() > 0) { + /* No page is selected, but there exist at least one available. + * => Select first page. + */ + this->SetSelectedPage(this->story_pages[0]->index); + } + + this->SetWidgetDisabledState(WID_SB_SEL_PAGE, this->story_pages.Length() == 0); + this->SetWidgetDirty(WID_SB_SEL_PAGE); + this->UpdatePrevNextDisabledState(); + } else if (data >= 0 && this->selected_page_id == data) { + this->RefreshSelectedPage(); + } + } +}; + +GUIStoryPageList::SortFunction * const StoryBookWindow::page_sorter_funcs[] = { + &PageOrderSorter, +}; + +GUIStoryPageElementList::SortFunction * const StoryBookWindow::page_element_sorter_funcs[] = { + &PageElementOrderSorter, +}; + +static const NWidgetPart _nested_story_book_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_BROWN), + NWidget(WWT_CAPTION, COLOUR_BROWN, WID_SB_CAPTION), SetDataTip(STR_STORY_BOOK_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_SHADEBOX, COLOUR_BROWN), + NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN), + NWidget(WWT_STICKYBOX, COLOUR_BROWN), + EndContainer(), + NWidget(NWID_HORIZONTAL), SetFill(1, 1), + NWidget(NWID_VERTICAL), SetFill(1, 1), + NWidget(WWT_PANEL, COLOUR_BROWN, WID_SB_PAGE_PANEL), SetResize(1, 1), SetScrollbar(WID_SB_SCROLLBAR), EndContainer(), + NWidget(NWID_HORIZONTAL), + 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), + NWidget(NWID_BUTTON_DROPDOWN, COLOUR_BROWN, WID_SB_SEL_PAGE), SetMinimalSize(93, 12), SetFill(1, 0), + SetDataTip(STR_BLACK_RAW_STRING, STR_STORY_BOOK_SEL_PAGE_TOOLTIP), SetResize(1, 0), + 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), + EndContainer(), + EndContainer(), + NWidget(NWID_VERTICAL), SetFill(0, 1), + NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_SB_SCROLLBAR), + NWidget(WWT_RESIZEBOX, COLOUR_BROWN), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _story_book_desc( + WDP_CENTER, "view_story", 400, 300, + WC_STORY_BOOK, WC_NONE, + 0, + _nested_story_book_widgets, lengthof(_nested_story_book_widgets) +); + +void ShowStoryBook() +{ + AllocateWindowDescFront(&_story_book_desc, 0); +} diff --git a/src/table/sprites.h b/src/table/sprites.h index cc24868314..576ffb88c5 100644 --- a/src/table/sprites.h +++ b/src/table/sprites.h @@ -56,7 +56,7 @@ static const SpriteID SPR_LARGE_SMALL_WINDOW = 682; /** Extra graphic spritenumbers */ static const SpriteID SPR_OPENTTD_BASE = 4896; -static const uint16 OPENTTD_SPRITE_COUNT = 169; +static const uint16 OPENTTD_SPRITE_COUNT = 174; /* Halftile-selection sprites */ static const SpriteID SPR_HALFTILE_SELECTION_FLAT = SPR_OPENTTD_BASE; @@ -1088,6 +1088,7 @@ static const SpriteID SPR_IMG_MESSAGES = 680; static const SpriteID SPR_IMG_QUERY = 723; static const SpriteID SPR_IMG_SIGN = 4082; static const SpriteID SPR_IMG_BUY_LAND = 4791; +static const SpriteID SPR_IMG_STORY_BOOK = SPR_OPENTTD_BASE + 169; /* OpenTTD in gamescreen */ static const SpriteID SPR_OTTD_O = 4842; @@ -1317,6 +1318,12 @@ static const SpriteID SPR_IMG_CONVERT_ELRAIL = SPR_OPENTTD_BASE + 59; static const SpriteID SPR_IMG_CONVERT_MONO = SPR_OPENTTD_BASE + 65; static const SpriteID SPR_IMG_CONVERT_MAGLEV = SPR_OPENTTD_BASE + 71; +/* story_gui.cpp */ +static const SpriteID SPR_IMG_VIEW_LOCATION = SPR_OPENTTD_BASE + 170; +static const SpriteID SPR_IMG_GOAL = SPR_OPENTTD_BASE + 171; +static const SpriteID SPR_IMG_GOAL_COMPLETED = SPR_OPENTTD_BASE + 172; +static const SpriteID SPR_IMG_GOAL_BROKEN_REF= SPR_OPENTTD_BASE + 173; + /* intro_gui.cpp, genworld_gui.cpp */ static const SpriteID SPR_SELECT_TEMPERATE = 4882; static const SpriteID SPR_SELECT_TEMPERATE_PUSHED = 4883; diff --git a/src/toolbar_gui.cpp b/src/toolbar_gui.cpp index 7d743d2768..cab5593eb5 100644 --- a/src/toolbar_gui.cpp +++ b/src/toolbar_gui.cpp @@ -642,7 +642,7 @@ static CallBackFunction MenuClickGraphs(int index) static CallBackFunction ToolbarLeagueClick(Window *w) { - PopupMainToolbMenu(w, WID_TN_LEAGUE, STR_GRAPH_MENU_GOAL, _networking ? 3 : 4, Game::GetInstance() != NULL ? 0 : 1); + PopupMainToolbMenu(w, WID_TN_LEAGUE, STR_GRAPH_MENU_GOAL, _networking ? 4 : 5, Game::GetInstance() != NULL ? 0 : 2); return CBF_NONE; } @@ -656,9 +656,10 @@ static CallBackFunction MenuClickLeague(int index) { switch (index) { case 0: ShowGoalsList(); break; - case 1: ShowCompanyLeagueTable(); break; - case 2: ShowPerformanceRatingDetail(); break; - case 3: ShowHighscoreTable(); break; + case 1: ShowStoryBook(); break; + case 2: ShowCompanyLeagueTable(); break; + case 3: ShowPerformanceRatingDetail(); break; + case 4: ShowHighscoreTable(); break; } return CBF_NONE; } diff --git a/src/widgets/story_widget.h b/src/widgets/story_widget.h new file mode 100644 index 0000000000..2c1e749146 --- /dev/null +++ b/src/widgets/story_widget.h @@ -0,0 +1,26 @@ + +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +/** @file goal_widget.h Types related to the goal widgets. */ + +#ifndef WIDGETS_STORY_WIDGET_H +#define WIDGETS_STORY_WIDGET_H + +/** Widgets of the #GoalListWindow class. */ +enum StoryBookWidgets { + WID_SB_CAPTION, ///< Caption of the window. + WID_SB_SEL_PAGE, ///< Page selector. + WID_SB_PAGE_PANEL,///< Page body. + WID_SB_SCROLLBAR, ///< Scrollbar of the goal list. + WID_SB_PREV_PAGE, ///< Prev button. + WID_SB_NEXT_PAGE, ///< Next button. +}; + +#endif /* WIDGETS_STORY_WIDGET_H */ \ No newline at end of file diff --git a/src/window_type.h b/src/window_type.h index d8365c9025..29fe15dc0e 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -284,6 +284,11 @@ enum WindowClass { */ WC_GOALS_LIST, + /** + * Story book; %Window numbers: + * - CompanyID = #StoryBookWidgets + */ + WC_STORY_BOOK, /** * Station list; %Window numbers: