OpenTTD-patches/src/newgrf_gui.cpp
rubidium af0616729a (svn r11433) -Fix: starting OpenTTD with DOS files made it look weird out of the box.
-Change: make extra sprites (the ones not in the TTD GRFs) replaceable using Action 5.
-Feature: make replacing contiguous subsets of sprites in for some types possible in Action 5.
Note to GRF authors: when you replaced OpenTTD sprites that are not from the TTD GRF files using Action A, your GRF will not have the intended result anymore as the sprite numbers have changed. You should replace the Action A with an Action 5 from now on.
2007-11-15 07:42:25 +00:00

594 lines
18 KiB
C++

/* $Id$ */
/** @file newgrf_gui.cpp */
#include "stdafx.h"
#include "openttd.h"
#include "functions.h"
#include "variables.h"
#include "gfx.h"
#include "gui.h"
#include "window.h"
#include "table/strings.h"
#include "table/sprites.h"
#include "newgrf.h"
#include "newgrf_config.h"
#include "strings.h"
#include "helpers.hpp"
/** Parse an integerlist string and set each found value
* @param p the string to be parsed. Each element in the list is seperated by a
* comma or a space character
* @param items pointer to the integerlist-array that will be filled with values
* @param maxitems the maximum number of elements the integerlist-array has
* @return returns the number of items found, or -1 on an error */
static int parse_intlist(const char *p, int *items, int maxitems)
{
int n = 0, v;
char *end;
for (;;) {
v = strtol(p, &end, 0);
if (p == end || n == maxitems) return -1;
p = end;
items[n++] = v;
if (*p == '\0') break;
if (*p != ',' && *p != ' ') return -1;
p++;
}
return n;
}
static void ShowNewGRFInfo(const GRFConfig *c, uint x, uint y, uint w, uint bottom, bool show_params)
{
char buff[256];
if (c->error != NULL) {
SetDParamStr(0, c->filename);
SetDParamStr(1, c->error->data);
for (uint i = 0; i < c->error->num_params; i++) {
uint32 param = 0;
byte param_number = c->error->param_number[i];
if (param_number < c->num_params) param = c->param[param_number];
SetDParam(2 + i, param);
}
char message[512];
GetString(message, c->error->custom_message != NULL ? BindCString(c->error->custom_message) : c->error->message, lastof(message));
SetDParamStr(0, message);
y += DrawStringMultiLine(x, y, c->error->severity, w, bottom - y);
}
/* Draw filename or not if it is not known (GRF sent over internet) */
if (c->filename != NULL) {
SetDParamStr(0, c->filename);
y += DrawStringMultiLine(x, y, STR_NEWGRF_FILENAME, w, bottom - y);
}
/* Prepare and draw GRF ID */
snprintf(buff, lengthof(buff), "%08X", BSWAP32(c->grfid));
SetDParamStr(0, buff);
y += DrawStringMultiLine(x, y, STR_NEWGRF_GRF_ID, w, bottom - y);
/* Prepare and draw MD5 sum */
md5sumToString(buff, lastof(buff), c->md5sum);
SetDParamStr(0, buff);
y += DrawStringMultiLine(x, y, STR_NEWGRF_MD5SUM, w, bottom - y);
/* Show GRF parameter list */
if (show_params) {
if (c->num_params > 0) {
GRFBuildParamList(buff, c, lastof(buff));
SetDParamStr(0, buff);
} else {
SetDParam(0, STR_01A9_NONE);
}
y += DrawStringMultiLine(x, y, STR_NEWGRF_PARAMETER, w, bottom - y);
}
/* Show flags */
if (c->status == GCS_NOT_FOUND) y += DrawStringMultiLine(x, y, STR_NEWGRF_NOT_FOUND, w, bottom - y);
if (c->status == GCS_DISABLED) y += DrawStringMultiLine(x, y, STR_NEWGRF_DISABLED, w, bottom - y);
if (HASBIT(c->flags, GCF_COMPATIBLE)) y += DrawStringMultiLine(x, y, STR_NEWGRF_COMPATIBLE_LOADED, w, bottom - y);
/* Draw GRF info if it exists */
if (c->info != NULL && !StrEmpty(c->info)) {
SetDParamStr(0, c->info);
y += DrawStringMultiLine(x, y, STR_02BD, w, bottom - y);
} else {
y += DrawStringMultiLine(x, y, STR_NEWGRF_NO_INFO, w, bottom - y);
}
}
/* Dialogue for adding NewGRF files to the selection */
struct newgrf_add_d {
GRFConfig **list;
const GRFConfig *sel;
};
static void NewGRFAddDlgWndProc(Window *w, WindowEvent *e)
{
switch (e->event) {
case WE_PAINT: {
const GRFConfig *c;
int y;
int n = 0;
/* Count the number of GRFs */
for (c = _all_grfs; c != NULL; c = c->next) n++;
w->vscroll.cap = (w->widget[3].bottom - w->widget[3].top) / 10;
SetVScrollCount(w, n);
SetWindowWidgetDisabledState(w, 6, WP(w, newgrf_add_d).sel == NULL || WP(w, newgrf_add_d).sel->IsOpenTTDBaseGRF());
DrawWindowWidgets(w);
GfxFillRect(w->widget[3].left + 1, w->widget[3].top + 1, w->widget[3].right, w->widget[3].bottom, 0xD7);
n = 0;
y = w->widget[3].top + 1;
for (c = _all_grfs; c != NULL; c = c->next) {
if (n >= w->vscroll.pos && n < w->vscroll.pos + w->vscroll.cap) {
bool h = c == WP(w, newgrf_add_d).sel;
const char *text = (c->name != NULL && !StrEmpty(c->name)) ? c->name : c->filename;
/* Draw selection background */
if (h) GfxFillRect(3, y, w->width - 15, y + 9, 156);
DoDrawStringTruncated(text, 4, y, h ? TC_WHITE : TC_ORANGE, w->width - 18);
y += 10;
}
n++;
}
if (WP(w, newgrf_add_d).sel != NULL) {
const Widget *wi = &w->widget[5];
ShowNewGRFInfo(WP(w, newgrf_add_d).sel, wi->left + 2, wi->top + 2, wi->right - wi->left - 2, wi->bottom, false);
}
break;
}
case WE_DOUBLE_CLICK:
if (e->we.click.widget != 3) break;
e->we.click.widget = 6;
/* Fall through */
case WE_CLICK:
switch (e->we.click.widget) {
case 3: {
/* Get row... */
const GRFConfig *c;
uint i = (e->we.click.pt.y - w->widget[3].top) / 10 + w->vscroll.pos;
for (c = _all_grfs; c != NULL && i > 0; c = c->next, i--);
WP(w, newgrf_add_d).sel = c;
SetWindowDirty(w);
break;
}
case 6: // Add selection to list
if (WP(w, newgrf_add_d).sel != NULL) {
const GRFConfig *src = WP(w, newgrf_add_d).sel;
GRFConfig **list;
/* Find last entry in the list, checking for duplicate grfid on the way */
for (list = WP(w, newgrf_add_d).list; *list != NULL; list = &(*list)->next) {
if ((*list)->grfid == src->grfid) {
ShowErrorMessage(INVALID_STRING_ID, STR_NEWGRF_DUPLICATE_GRFID, 0, 0);
return;
}
}
/* Copy GRF details from scanned list */
GRFConfig *c = CallocT<GRFConfig>(1);
*c = *src;
c->filename = strdup(src->filename);
if (src->name != NULL) c->name = strdup(src->name);
if (src->info != NULL) c->info = strdup(src->info);
c->next = NULL;
/* Append GRF config to configuration list */
*list = c;
DeleteWindowByClass(WC_SAVELOAD);
InvalidateWindowData(WC_GAME_OPTIONS, 0);
}
break;
case 7: // Rescan list
WP(w, newgrf_add_d).sel = NULL;
ScanNewGRFFiles();
SetWindowDirty(w);
break;
}
break;
}
}
static const Widget _newgrf_add_dlg_widgets[] = {
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW },
{ WWT_CAPTION, RESIZE_RIGHT, 14, 11, 306, 0, 13, STR_NEWGRF_ADD_CAPTION, STR_018C_WINDOW_TITLE_DRAG_THIS },
/* List of files */
{ WWT_PANEL, RESIZE_RB, 14, 0, 294, 14, 121, 0x0, STR_NULL },
{ WWT_INSET, RESIZE_RB, 14, 2, 292, 16, 119, 0x0, STR_NULL },
{ WWT_SCROLLBAR, RESIZE_LRB, 14, 295, 306, 14, 121, 0x0, STR_NULL },
/* NewGRF file info */
{ WWT_PANEL, RESIZE_RTB, 14, 0, 306, 122, 224, 0x0, STR_NULL },
{ WWT_PUSHTXTBTN, RESIZE_RTB, 14, 0, 146, 225, 236, STR_NEWGRF_ADD_FILE, STR_NEWGRF_ADD_FILE_TIP },
{ WWT_PUSHTXTBTN, RESIZE_LRTB, 14, 147, 294, 225, 236, STR_NEWGRF_RESCAN_FILES, STR_NEWGRF_RESCAN_FILES_TIP },
{ WWT_RESIZEBOX, RESIZE_LRTB, 14, 295, 306, 225, 236, 0x0, STR_RESIZE_BUTTON },
{ WIDGETS_END },
};
static const WindowDesc _newgrf_add_dlg_desc = {
WDP_CENTER, WDP_CENTER, 307, 237, 307, 337,
WC_SAVELOAD, WC_NONE,
WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
_newgrf_add_dlg_widgets,
NewGRFAddDlgWndProc,
};
/* 'NewGRF Settings' dialogue */
struct newgrf_d {
GRFConfig **orig_list; ///< grf list the window is shown with
GRFConfig **list; ///< temporary grf list to which changes are made
GRFConfig *sel; ///< selected grf item
bool editable; ///< is the window editable
bool show_params; ///< are the grf-parameters shown in the info-panel
bool execute; ///< on pressing 'apply changes' are grf changes applied immediately, or only list is updated
};
assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(newgrf_d));
enum ShowNewGRFStateWidgets {
SNGRFS_ADD = 3,
SNGRFS_REMOVE,
SNGRFS_MOVE_UP,
SNGRFS_MOVE_DOWN,
SNGRFS_FILE_LIST = 7,
SNGRFS_NEWGRF_INFO = 9,
SNGRFS_SET_PARAMETERS,
SNGRFS_APPLY_CHANGES,
};
static void SetupNewGRFState(Window *w)
{
bool disable_all = WP(w, newgrf_d).sel == NULL || !WP(w, newgrf_d).editable;
SetWindowWidgetDisabledState(w, 3, !WP(w, newgrf_d).editable);
SetWindowWidgetsDisabledState(w, disable_all,
SNGRFS_REMOVE,
SNGRFS_MOVE_UP,
SNGRFS_MOVE_DOWN,
WIDGET_LIST_END
);
SetWindowWidgetDisabledState(w, SNGRFS_SET_PARAMETERS, !WP(w, newgrf_d).show_params || disable_all);
if (!disable_all) {
/* All widgets are now enabled, so disable widgets we can't use */
if (WP(w, newgrf_d).sel == *WP(w, newgrf_d).list) DisableWindowWidget(w, SNGRFS_MOVE_UP);
if (WP(w, newgrf_d).sel->next == NULL) DisableWindowWidget(w, SNGRFS_MOVE_DOWN);
if (WP(w, newgrf_d).sel->IsOpenTTDBaseGRF()) DisableWindowWidget(w, SNGRFS_REMOVE);
}
}
static void SetupNewGRFWindow(Window *w)
{
const GRFConfig *c;
int i;
for (c = *WP(w, newgrf_d).list, i = 0; c != NULL; c = c->next, i++);
w->vscroll.cap = (w->widget[SNGRFS_FILE_LIST].bottom - w->widget[SNGRFS_FILE_LIST].top) / 14 + 1;
SetVScrollCount(w, i);
SetWindowWidgetDisabledState(w, SNGRFS_APPLY_CHANGES, !WP(w, newgrf_d).editable);
}
/** Callback function for the newgrf 'apply changes' confirmation window
* @param w Window which is calling this callback
* @param confirmed boolean value, true when yes was clicked, false otherwise
*/
static void NewGRFConfirmationCallback(Window *w, bool confirmed)
{
if (confirmed) {
newgrf_d *nd = &WP(w, newgrf_d);
GRFConfig *c;
int i = 0;
CopyGRFConfigList(nd->orig_list, *nd->list, false);
ReloadNewGRFData();
/* Show new, updated list */
for (c = *nd->list; c != NULL && c != nd->sel; c = c->next, i++);
CopyGRFConfigList(nd->list, *nd->orig_list, false);
for (c = *nd->list; c != NULL && i > 0; c = c->next, i--);
nd->sel = c;
SetWindowDirty(w);
}
}
static void NewGRFWndProc(Window *w, WindowEvent *e)
{
switch (e->event) {
case WE_PAINT: {
const GRFConfig *c;
int i, y;
SetupNewGRFState(w);
DrawWindowWidgets(w);
/* Draw NewGRF list */
y = w->widget[SNGRFS_FILE_LIST].top;
for (c = *WP(w, newgrf_d).list, i = 0; c != NULL; c = c->next, i++) {
if (i >= w->vscroll.pos && i < w->vscroll.pos + w->vscroll.cap) {
const char *text = (c->name != NULL && !StrEmpty(c->name)) ? c->name : c->filename;
SpriteID pal;
byte txtoffset;
/* Pick a colour */
switch (c->status) {
case GCS_NOT_FOUND:
case GCS_DISABLED:
pal = PALETTE_TO_RED;
break;
case GCS_ACTIVATED:
pal = PALETTE_TO_GREEN;
break;
default:
pal = PALETTE_TO_BLUE;
break;
}
/* Do not show a "not-failure" colour when it actually failed to load */
if (pal != PALETTE_TO_RED) {
if (HASBIT(c->flags, GCF_STATIC)) {
pal = PALETTE_TO_GREY;
} else if (HASBIT(c->flags, GCF_COMPATIBLE)) {
pal = PALETTE_TO_ORANGE;
}
}
DrawSprite(SPR_SQUARE, pal, 5, y + 2);
if (c->error != NULL) DrawSprite(SPR_WARNING_SIGN, 0, 20, y + 2);
txtoffset = c->error != NULL ? 35 : 25;
DoDrawStringTruncated(text, txtoffset, y + 3, WP(w, newgrf_d).sel == c ? TC_WHITE : TC_BLACK, w->width - txtoffset - 10);
y += 14;
}
}
if (WP(w, newgrf_d).sel != NULL) {
/* Draw NewGRF file info */
const Widget *wi = &w->widget[SNGRFS_NEWGRF_INFO];
ShowNewGRFInfo(WP(w, newgrf_d).sel, wi->left + 2, wi->top + 2, wi->right - wi->left - 2, wi->bottom, WP(w, newgrf_d).show_params);
}
break;
}
case WE_INVALIDATE_DATA:
SetupNewGRFWindow(w);
break;
case WE_CLICK:
switch (e->we.click.widget) {
case SNGRFS_ADD: { // Add GRF
GRFConfig **list = WP(w, newgrf_d).list;
Window *w;
DeleteWindowByClass(WC_SAVELOAD);
w = AllocateWindowDesc(&_newgrf_add_dlg_desc);
w->resize.step_height = 10;
WP(w, newgrf_add_d).list = list;
break;
}
case SNGRFS_REMOVE: { // Remove GRF
GRFConfig **pc, *c, *newsel;
/* Choose the next GRF file to be the selected file */
newsel = WP(w, newgrf_d).sel->next;
for (pc = WP(w, newgrf_d).list; (c = *pc) != NULL; pc = &c->next) {
/* If the new selection is empty (i.e. we're deleting the last item
* in the list, pick the file just before the selected file */
if (newsel == NULL && c->next == WP(w, newgrf_d).sel) newsel = c;
if (c == WP(w, newgrf_d).sel) {
*pc = c->next;
free(c);
break;
}
}
WP(w, newgrf_d).sel = newsel;
SetupNewGRFWindow(w);
SetWindowDirty(w);
break;
}
case SNGRFS_MOVE_UP: { // Move GRF up
GRFConfig **pc, *c;
if (WP(w, newgrf_d).sel == NULL) break;
for (pc = WP(w, newgrf_d).list; (c = *pc) != NULL; pc = &c->next) {
if (c->next == WP(w, newgrf_d).sel) {
c->next = WP(w, newgrf_d).sel->next;
WP(w, newgrf_d).sel->next = c;
*pc = WP(w, newgrf_d).sel;
break;
}
}
SetWindowDirty(w);
break;
}
case SNGRFS_MOVE_DOWN: { // Move GRF down
GRFConfig **pc, *c;
if (WP(w, newgrf_d).sel == NULL) break;
for (pc = WP(w, newgrf_d).list; (c = *pc) != NULL; pc = &c->next) {
if (c == WP(w, newgrf_d).sel) {
*pc = c->next;
c->next = c->next->next;
(*pc)->next = c;
break;
}
}
SetWindowDirty(w);
break;
}
case SNGRFS_FILE_LIST: { // Select a GRF
GRFConfig *c;
uint i = (e->we.click.pt.y - w->widget[SNGRFS_FILE_LIST].top) / 14 + w->vscroll.pos;
for (c = *WP(w, newgrf_d).list; c != NULL && i > 0; c = c->next, i--);
WP(w, newgrf_d).sel = c;
SetWindowDirty(w);
break;
}
case SNGRFS_APPLY_CHANGES: // Apply changes made to GRF list
if (WP(w, newgrf_d).execute) {
ShowQuery(
STR_POPUP_CAUTION_CAPTION,
STR_NEWGRF_CONFIRMATION_TEXT,
w,
NewGRFConfirmationCallback
);
} else {
CopyGRFConfigList(WP(w, newgrf_d).orig_list, *WP(w, newgrf_d).list, true);
ResetGRFConfig(false);
ReloadNewGRFData();
}
break;
case SNGRFS_SET_PARAMETERS: { // Edit parameters
char buff[512];
if (WP(w, newgrf_d).sel == NULL) break;
GRFBuildParamList(buff, WP(w, newgrf_d).sel, lastof(buff));
ShowQueryString(BindCString(buff), STR_NEWGRF_PARAMETER_QUERY, 63, 250, w, CS_ALPHANUMERAL);
break;
}
}
break;
case WE_ON_EDIT_TEXT:
if (e->we.edittext.str != NULL) {
/* Parse our new "int list" */
GRFConfig *c = WP(w, newgrf_d).sel;
c->num_params = parse_intlist(e->we.edittext.str, (int*)c->param, lengthof(c->param));
/* parse_intlist returns -1 on error */
if (c->num_params == (byte)-1) c->num_params = 0;
}
SetWindowDirty(w);
break;
case WE_DESTROY:
if (!WP(w, newgrf_d).execute) {
CopyGRFConfigList(WP(w, newgrf_d).orig_list, *WP(w, newgrf_d).list, true);
ResetGRFConfig(false);
ReloadNewGRFData();
}
/* Remove the temporary copy of grf-list used in window */
ClearGRFConfigList(WP(w, newgrf_d).list);
break;
case WE_RESIZE:
w->vscroll.cap += e->we.sizing.diff.y / 14;
w->widget[SNGRFS_FILE_LIST].data = (w->vscroll.cap << 8) + 1;
SetupNewGRFWindow(w);
break;
}
}
static const Widget _newgrf_widgets[] = {
{ WWT_CLOSEBOX, RESIZE_NONE, 10, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW },
{ WWT_CAPTION, RESIZE_RIGHT, 10, 11, 299, 0, 13, STR_NEWGRF_SETTINGS_CAPTION, STR_018C_WINDOW_TITLE_DRAG_THIS },
/* NewGRF file Add, Remove, Move up, Move down */
{ WWT_PANEL, RESIZE_RIGHT, 10, 0, 299, 14, 29, STR_NULL, STR_NULL },
{ WWT_PUSHTXTBTN, RESIZE_NONE, 3, 10, 79, 16, 27, STR_NEWGRF_ADD, STR_NEWGRF_ADD_TIP },
{ WWT_PUSHTXTBTN, RESIZE_NONE, 3, 80, 149, 16, 27, STR_NEWGRF_REMOVE, STR_NEWGRF_REMOVE_TIP },
{ WWT_PUSHTXTBTN, RESIZE_NONE, 3, 150, 219, 16, 27, STR_NEWGRF_MOVEUP, STR_NEWGRF_MOVEUP_TIP },
{ WWT_PUSHTXTBTN, RESIZE_NONE, 3, 220, 289, 16, 27, STR_NEWGRF_MOVEDOWN, STR_NEWGRF_MOVEDOWN_TIP },
/* NewGRF file list */
{ WWT_MATRIX, RESIZE_RB, 10, 0, 287, 30, 99, 0x501, STR_NEWGRF_FILE_TIP },
{ WWT_SCROLLBAR, RESIZE_LRB, 10, 288, 299, 30, 99, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST },
/* NewGRF file info */
{ WWT_PANEL, RESIZE_RTB, 10, 0, 299, 100, 212, STR_NULL, STR_NULL },
/* Edit parameter and apply changes button... */
{ WWT_PUSHTXTBTN, RESIZE_TB, 10, 0, 143, 213, 224, STR_NEWGRF_SET_PARAMETERS, STR_NULL },
{ WWT_PUSHTXTBTN, RESIZE_RTB, 10, 144, 287, 213, 224, STR_NEWGRF_APPLY_CHANGES, STR_NULL },
{ WWT_RESIZEBOX, RESIZE_LRTB, 10, 288, 299, 213, 224, 0x0, STR_RESIZE_BUTTON },
{ WIDGETS_END },
};
static const WindowDesc _newgrf_desc = {
WDP_CENTER, WDP_CENTER, 300, 225, 300, 225,
WC_GAME_OPTIONS, WC_NONE,
WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
_newgrf_widgets,
NewGRFWndProc,
};
/** Setup the NewGRF gui
* @param editable allow the user to make changes to the grfconfig in the window
* @param show_params show information about what parameters are set for the grf files
* @param exec_changes if changes are made to the list (editable is true), apply these
* changes immediately or only update the list
* @param config pointer to a linked-list of grfconfig's that will be shown */
void ShowNewGRFSettings(bool editable, bool show_params, bool exec_changes, GRFConfig **config)
{
static GRFConfig *local = NULL;
Window *w;
DeleteWindowByClass(WC_GAME_OPTIONS);
w = AllocateWindowDesc(&_newgrf_desc);
if (w == NULL) return;
w->resize.step_height = 14;
CopyGRFConfigList(&local, *config, false);
/* Clear selections */
WP(w, newgrf_d).sel = NULL;
WP(w, newgrf_d).list = &local;
WP(w, newgrf_d).orig_list = config;
WP(w, newgrf_d).editable = editable;
WP(w, newgrf_d).execute = exec_changes;
WP(w, newgrf_d).show_params = show_params;
SetupNewGRFWindow(w);
}