diff --git a/src/fileio_func.h b/src/fileio_func.h --- a/src/fileio_func.h +++ b/src/fileio_func.h @@ -66,6 +66,8 @@ extern char *_personal_dir; ///< custom directory for personal settings, saves, newgrf, etc. +static const uint16 MAX_README_LENGTH = 8191; + /** Helper for scanning for files with a given name */ class FileScanner { diff --git a/src/lang/english.txt b/src/lang/english.txt --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2390,6 +2390,7 @@ STR_NEWGRF_SETTINGS_MOVEDOWN :{BLACK}Move Down STR_NEWGRF_SETTINGS_MOVEDOWN_TOOLTIP :{BLACK}Move the selected NewGRF file down the list STR_NEWGRF_SETTINGS_FILE_TOOLTIP :{BLACK}A list of the NewGRF files that are installed. Click a file to change its parameters +STR_NEWGRF_SETTINGS_VIEW_README :{BLACK}View Readme STR_NEWGRF_SETTINGS_SET_PARAMETERS :{BLACK}Set parameters STR_NEWGRF_SETTINGS_TOGGLE_PALETTE :{BLACK}Toggle palette @@ -2423,6 +2424,9 @@ STR_NEWGRF_PARAMETERS_SETTING :{STRING1}: {ORANGE}{STRING1} STR_NEWGRF_PARAMETERS_NUM_PARAM :{LTBLUE}Number of parameters: {ORANGE}{NUM} +# NewGRF readme window +STR_NEWGRF_README_CAPTION :{WHITE}View NewGRF Readme + # NewGRF inspect window STR_NEWGRF_INSPECT_CAPTION :{WHITE}Inspect - {STRING5} STR_NEWGRF_INSPECT_PARENT_BUTTON :{BLACK}Parent diff --git a/src/newgrf_config.cpp b/src/newgrf_config.cpp --- a/src/newgrf_config.cpp +++ b/src/newgrf_config.cpp @@ -20,6 +20,7 @@ #include "fileio_func.h" #include "fios.h" +#include "tar_type.h" /** Create a new GRFTextWrapper. */ GRFTextWrapper::GRFTextWrapper() : @@ -761,3 +762,27 @@ { return (this->ident.grfid & 0x00FFFFFF) == OPENTTD_GRAPHICS_BASE_GRF_ID; } + +/** + * Check a grf for a readme.txt file. + * @param cfg The GRFConfig to check. + * @return true is the GRF is packaged with a readme.txt file. + * @note a GRF can only have a readme if it is inside a tar archive. + */ +bool GrfHasReadme(const GRFConfig *cfg) +{ + if (cfg == NULL || cfg->filename == NULL) return false; + + const char *slash = strrchr(cfg->filename, PATHSEPCHAR); + if (slash == NULL) return false; + int prefix_length = slash - cfg->filename + 1; // Including the path separator. + if (prefix_length + 10 + 1 > MAX_PATH) return false; // "foo/" + "readme.txt" + '\0' must fit. + + char readme_path[MAX_PATH]; + memcpy(readme_path, cfg->filename, prefix_length); + strecpy(readme_path + prefix_length, "readme.txt", lastof(readme_path)); + + TarFileList::iterator tar_entry = _tar_filelist.find(readme_path); + if (tar_entry == _tar_filelist.end()) return false; + return true; +} diff --git a/src/newgrf_config.h b/src/newgrf_config.h --- a/src/newgrf_config.h +++ b/src/newgrf_config.h @@ -200,6 +200,7 @@ GRFListCompatibility IsGoodGRFConfigList(GRFConfig *grfconfig); bool FillGRFDetails(GRFConfig *config, bool is_static); char *GRFBuildParamList(char *dst, const GRFConfig *c, const char *last); +bool GrfHasReadme(const GRFConfig *cfg); /* In newgrf_gui.cpp */ void ShowNewGRFSettings(bool editable, bool show_params, bool exec_changes, GRFConfig **config); diff --git a/src/newgrf_gui.cpp b/src/newgrf_gui.cpp --- a/src/newgrf_gui.cpp +++ b/src/newgrf_gui.cpp @@ -25,6 +25,7 @@ #include "core/geometry_func.hpp" #include "newgrf_text.h" #include "fileio_func.h" +#include "tar_type.h" #include "table/strings.h" #include "table/sprites.h" @@ -449,17 +450,121 @@ /* Window definition for the change grf parameters window */ static const WindowDesc _newgrf_parameters_desc( WDP_CENTER, 500, 208, - WC_GRF_PARAMETERS, WC_NONE, + WC_NEWGRF_PARAMETERS, WC_NONE, WDF_UNCLICK_BUTTONS, _nested_newgrf_parameter_widgets, lengthof(_nested_newgrf_parameter_widgets) ); void OpenGRFParameterWindow(GRFConfig *c) { - DeleteWindowByClass(WC_GRF_PARAMETERS); + DeleteWindowByClass(WC_NEWGRF_PARAMETERS); new NewGRFParametersWindow(&_newgrf_parameters_desc, c); } +enum ShowNewGRFReadmeWidgets { + GRW_WIDGET_BACKGROUND, ///< Panel to draw the readme on + GRW_WIDGET_SCROLLBAR, ///< Scrollbar to scroll through the readme +}; + +struct NewGRFReadmeWindow : public Window { + GRFConfig *grf_config; ///< View the readme of this GRFConfig. + uint clicked_button; ///< The row in which a button was clicked or UINT_MAX. + bool clicked_increase; ///< True if the increase button was clicked, false for the decrease button. + int timeout; ///< How long before we unpress the last-pressed button? + int line_height; ///< Height of a row in the matrix widget. + Scrollbar *vscroll; + + + static const int top_spacing; ///< Additional spacing at the top of the #MHW_BACKGROUND widget. + static const int bottom_spacing; ///< Additional spacing at the bottom of the #MHW_BACKGROUND widget. + + int date_width; /// < Width needed for the date part. + + NewGRFReadmeWindow(const WindowDesc *desc, GRFConfig *c) : Window(), + grf_config(c), + clicked_button(UINT_MAX), + timeout(0) + { + this->CreateNestedTree(desc); + this->vscroll = this->GetScrollbar(GRFPAR_WIDGET_SCROLLBAR); + this->FinishInitNested(desc); // Initializes 'this->line_height' as side effect. + this->OnInvalidateData(); + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) + { + switch (widget) { + case GRW_WIDGET_BACKGROUND: + this->line_height = FONT_HEIGHT_NORMAL + 2; + resize->height = this->line_height; + + size->height = 4 * resize->height + this->top_spacing + this->bottom_spacing; // At least 4 lines are visible. + size->width = max(200u, size->width); // At least 200 pixels wide. + break; + } + } + + virtual void OnPaint() + { + this->OnInvalidateData(0); + this->DrawWidgets(); + } + + virtual void DrawWidget(const Rect &r, int widget) const + { + if (widget != GRW_WIDGET_BACKGROUND) return; + } + + virtual void OnResize() + { + } + + /** + * Some data on this window has become invalid. + * @param data Information about the changed data. + * @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; + DeleteChildWindows(WC_NEWGRF_README); + } +}; + +const int NewGRFReadmeWindow::top_spacing = WD_FRAMERECT_TOP + 4; +const int NewGRFReadmeWindow::bottom_spacing = WD_FRAMERECT_BOTTOM; + +static const NWidgetPart _nested_newgrf_readme_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_MAUVE), + NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_NEWGRF_README_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_SHADEBOX, COLOUR_MAUVE), + NWidget(WWT_STICKYBOX, COLOUR_MAUVE), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_MAUVE, GRW_WIDGET_BACKGROUND), SetMinimalSize(200, 125), SetResize(1, 12), SetScrollbar(GRW_WIDGET_SCROLLBAR), + EndContainer(), + NWidget(NWID_VERTICAL), + NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, GRW_WIDGET_SCROLLBAR), + NWidget(WWT_RESIZEBOX, COLOUR_MAUVE), + EndContainer(), + EndContainer(), +}; + +/* Window definition for the grf readme window */ +static const WindowDesc _newgrf_readme_desc( + WDP_CENTER, 630, 460, + WC_NEWGRF_README, WC_NONE, + WDF_UNCLICK_BUTTONS, + _nested_newgrf_readme_widgets, lengthof(_nested_newgrf_readme_widgets) +); + +void OpenNewGRFReadmeWindow(GRFConfig *c) +{ + DeleteWindowByClass(WC_NEWGRF_README); + new NewGRFReadmeWindow(&_newgrf_readme_desc, c); +} + static GRFPresetList _grf_preset_list; class DropDownListPresetItem : public DropDownListItem { @@ -497,6 +602,7 @@ SNGRFS_SCROLL2BAR, SNGRFS_NEWGRF_INFO_TITLE, SNGRFS_NEWGRF_INFO, + SNGRFS_NEWGRF_README, SNGRFS_SET_PARAMETERS, SNGRFS_TOGGLE_PALETTE, SNGRFS_APPLY_CHANGES, @@ -574,7 +680,8 @@ ~NewGRFWindow() { - DeleteWindowByClass(WC_GRF_PARAMETERS); + DeleteWindowByClass(WC_NEWGRF_PARAMETERS); + DeleteWindowByClass(WC_NEWGRF_README); if (this->editable && !this->execute) { CopyGRFConfigList(this->orig_list, this->actives, true); @@ -847,7 +954,7 @@ GRFConfig *c; for (c = this->actives; c != NULL && i > 0; c = c->next, i--) {} - if (this->active_sel != c) DeleteWindowByClass(WC_GRF_PARAMETERS); + if (this->active_sel != c) DeleteWindowByClass(WC_NEWGRF_PARAMETERS); this->active_sel = c; this->avail_sel = NULL; this->avail_pos = -1; @@ -859,7 +966,7 @@ case SNGRFS_REMOVE: { // Remove GRF if (this->active_sel == NULL || !this->editable) break; - DeleteWindowByClass(WC_GRF_PARAMETERS); + DeleteWindowByClass(WC_NEWGRF_PARAMETERS); /* Choose the next GRF file to be the selected file. */ GRFConfig *newsel = this->active_sel->next; @@ -888,7 +995,7 @@ case SNGRFS_AVAIL_LIST: { // Select a non-active GRF. uint i = this->vscroll2->GetScrolledRowFromWidget(pt.y, this, SNGRFS_AVAIL_LIST); this->active_sel = NULL; - DeleteWindowByClass(WC_GRF_PARAMETERS); + DeleteWindowByClass(WC_NEWGRF_PARAMETERS); if (i < this->avails.Length()) { this->avail_sel = this->avails[i]; this->avail_pos = i; @@ -941,6 +1048,18 @@ } this->DeleteChildWindows(WC_QUERY_STRING); // Remove the parameter query window break; + + case SNGRFS_NEWGRF_README: { // View GRF readme + if (this->active_sel == NULL && this->avail_sel == NULL) break; + + if (this->active_sel) { + OpenNewGRFReadmeWindow(this->active_sel); + break; + } + GRFConfig *c = new GRFConfig(*this->avail_sel); + OpenNewGRFReadmeWindow(c); + break; + } case SNGRFS_SET_PARAMETERS: { // Edit parameters if (this->active_sel == NULL || !this->editable || !this->show_params || this->active_sel->num_valid_params == 0) break; @@ -991,6 +1110,7 @@ this->avails.ForceRebuild(); this->InvalidateData(GOID_NEWGRF_RESCANNED); this->DeleteChildWindows(WC_QUERY_STRING); // Remove the parameter query window + this->DeleteChildWindows(WC_NEWGRF_README); // Remove the view readme window InvalidateWindowClassesData(WC_SAVELOAD); break; } @@ -1008,7 +1128,7 @@ } this->avails.ForceRebuild(); - DeleteWindowByClass(WC_GRF_PARAMETERS); + DeleteWindowByClass(WC_NEWGRF_PARAMETERS); this->active_sel = NULL; this->InvalidateData(GOID_NEWGRF_PRESET_LOADED); } @@ -1098,6 +1218,13 @@ SNGRFS_MOVE_DOWN, WIDGET_LIST_END ); + + bool disable_readme = false; + if (this->avail_sel == NULL && this->active_sel == NULL) disable_readme = true; + const GRFConfig *c = (this->avail_sel == NULL ? this->active_sel : this->avail_sel); + if (GrfHasReadme(c)) disable_readme = true; + this->SetWidgetDisabledState(SNGRFS_NEWGRF_README, disable_readme); + this->SetWidgetDisabledState(SNGRFS_SET_PARAMETERS, !this->show_params || disable_all || this->active_sel->num_valid_params == 0); this->SetWidgetDisabledState(SNGRFS_TOGGLE_PALETTE, disable_all); @@ -1582,6 +1709,8 @@ NWidget(WWT_PANEL, COLOUR_MAUVE), SetPadding(0, 0, 2, 0), NWidget(WWT_EMPTY, COLOUR_MAUVE, SNGRFS_NEWGRF_INFO_TITLE), SetFill(1, 0), SetResize(1, 0), NWidget(WWT_EMPTY, COLOUR_MAUVE, SNGRFS_NEWGRF_INFO), SetFill(1, 1), SetResize(1, 1), SetMinimalSize(150, 100), + NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, SNGRFS_NEWGRF_README), SetFill(1, 0), SetResize(1, 0), + SetDataTip(STR_NEWGRF_SETTINGS_VIEW_README, STR_NULL), SetPadding(2, 2, 2, 2), EndContainer(), NWidget(NWID_SELECTION, INVALID_COLOUR, SNGRFS_SHOW_APPLY), /* Right side, buttons. */ @@ -1645,7 +1774,7 @@ static void NewGRFConfirmationCallback(Window *w, bool confirmed) { if (confirmed) { - DeleteWindowByClass(WC_GRF_PARAMETERS); + DeleteWindowByClass(WC_NEWGRF_PARAMETERS); NewGRFWindow *nw = dynamic_cast(w); GamelogStartAction(GLAT_GRF); diff --git a/src/window_type.h b/src/window_type.h --- a/src/window_type.h +++ b/src/window_type.h @@ -109,8 +109,9 @@ WC_NEWGRF_INSPECT, WC_SPRITE_ALIGNER, WC_INDUSTRY_CARGOES, - WC_GRF_PARAMETERS, + WC_NEWGRF_PARAMETERS, WC_BUILD_OBJECT, + WC_NEWGRF_README, WC_INVALID = 0xFFFF };