tron@2186: /* $Id$ */ tron@2186: belugas@6527: /** @file industry_gui.cpp */ belugas@6527: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" tron@1299: #include "debug.h" tron@2163: #include "functions.h" tron@1309: #include "strings.h" tron@507: #include "table/strings.h" Darkvater@1914: #include "table/sprites.h" tron@679: #include "map.h" tron@1386: #include "gui.h" truelight@0: #include "window.h" truelight@0: #include "gfx.h" truelight@0: #include "command.h" truelight@0: #include "viewport.h" truelight@0: #include "industry.h" truelight@0: #include "town.h" tron@2159: #include "variables.h" rubidium@5838: #include "helpers.hpp" peter1138@6417: #include "cargotype.h" rubidium@7691: #include "newgrf.h" rubidium@7691: #include "newgrf_callbacks.h" rubidium@7691: #include "newgrf_industries.h" rubidium@7691: #include "newgrf_text.h" belugas@7779: #include "date.h" truelight@0: belugas@7714: extern Industry *CreateNewIndustry(TileIndex tile, IndustryType type); belugas@7714: belugas@7714: /** belugas@7714: * Search callback function for TryBuildIndustry belugas@7714: * @param tile to test belugas@7714: * @param data that is passed by the caller. In this case, the type of industry been tested belugas@7714: * @return the success (or not) of the operation belugas@7714: */ belugas@7714: static bool SearchTileForIndustry(TileIndex tile, uint32 data) belugas@7714: { belugas@7714: return CreateNewIndustry(tile, data) != NULL; belugas@7714: } belugas@7714: belugas@7714: /** belugas@7714: * Perform a 9*9 tiles circular search around a tile belugas@7714: * in order to find a suitable zone to create the desired industry belugas@7714: * @param tile to start search for belugas@7714: * @param type of the desired industry belugas@7714: * @return the success (or not) of the operation belugas@7714: */ belugas@7714: static bool TryBuildIndustry(TileIndex tile, int type) belugas@7714: { belugas@7714: return CircularTileSearch(tile, 9, SearchTileForIndustry, type); belugas@7714: } belugas@7714: bool _ignore_restrictions; belugas@7714: belugas@7714: enum { belugas@7714: DYNA_INDU_MATRIX_WIDGET = 2, belugas@7714: DYNA_INDU_INFOPANEL = 4, belugas@7714: DYNA_INDU_FUND_WIDGET, belugas@7714: DYNA_INDU_RESIZE_WIDGET, belugas@4995: }; truelight@0: belugas@7778: /** Attached struct to the window extended data */ belugas@7778: struct fnd_d { belugas@7778: int index; ///< index of the element in the matrix belugas@7778: IndustryType select; ///< industry corresponding to the above index belugas@7779: uint16 callback_timer; ///< timer counter for callback eventual verification belugas@7779: bool timer_enabled; ///< timer can be used belugas@7778: }; belugas@7778: assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(fnd_d)); belugas@7778: belugas@7776: /** Helper struct holding the available industries for current situation */ belugas@7714: static struct IndustryData { belugas@7776: uint16 count; ///< How many industries are loaded belugas@7778: IndustryType index[NUM_INDUSTRYTYPES + 1]; ///< Type of industry, in the order it was loaded belugas@7776: StringID text[NUM_INDUSTRYTYPES + 1]; ///< Text coming from CBM_IND_FUND_MORE_TEXT (if ever) belugas@7779: bool enabled[NUM_INDUSTRYTYPES + 1]; ///< availability state, coming from CBID_INDUSTRY_AVAILABLE (if ever) belugas@7776: } _fund_gui; miham@1004: belugas@7714: static void BuildDynamicIndustryWndProc(Window *w, WindowEvent *e) truelight@0: { tron@2951: switch (e->event) { belugas@7714: case WE_CREATE: { belugas@7714: IndustryType ind; belugas@7714: const IndustrySpec *indsp; truelight@0: belugas@7714: /* Shorten the window to the equivalant of the additionnal purchase belugas@7714: * info coming from the callback. SO it will only be available to tis full belugas@7714: * height when newindistries are loaded */ belugas@7714: if (!_loaded_newgrf_features.has_newindustries) { belugas@7714: w->widget[DYNA_INDU_INFOPANEL].bottom -= 44; belugas@7714: w->widget[DYNA_INDU_FUND_WIDGET].bottom -= 44; belugas@7714: w->widget[DYNA_INDU_FUND_WIDGET].top -= 44; belugas@7714: w->widget[DYNA_INDU_RESIZE_WIDGET].bottom -= 44; belugas@7714: w->widget[DYNA_INDU_RESIZE_WIDGET].top -= 44; belugas@7714: w->resize.height = w->height -= 44; belugas@7714: } truelight@0: belugas@7779: WP(w, fnd_d).timer_enabled = _loaded_newgrf_features.has_newindustries; belugas@7779: belugas@7714: /* Initilialize structures */ belugas@7776: memset(&_fund_gui.index, 0xFF, NUM_INDUSTRYTYPES); belugas@7776: memset(&_fund_gui.text, STR_NULL, NUM_INDUSTRYTYPES); belugas@7779: memset(&_fund_gui.enabled, false, NUM_INDUSTRYTYPES); belugas@7776: _fund_gui.count = 0; belugas@7714: belugas@7714: w->vscroll.cap = 8; // rows in grid, same in scroller belugas@7714: w->resize.step_height = 13; belugas@7714: belugas@7714: if (_game_mode == GM_EDITOR) { // give room for the Many Random "button" belugas@7776: _fund_gui.index[_fund_gui.count] = INVALID_INDUSTRYTYPE; belugas@7776: _fund_gui.count++; belugas@7779: WP(w, fnd_d).timer_enabled = false; belugas@7714: } belugas@7714: belugas@7714: /* We'll perform two distinct loops, one for secondary industries, and the other one for belugas@7776: * primary ones. Each loop will fill the _fund_gui structure. */ rubidium@7815: for (ind = 0; ind < NUM_INDUSTRYTYPES; ind++) { belugas@7714: indsp = GetIndustrySpec(ind); belugas@7714: if (indsp->enabled && (!indsp->IsRawIndustry() || _game_mode == GM_EDITOR)) { belugas@7776: _fund_gui.index[_fund_gui.count] = ind; glx@8143: _fund_gui.enabled[_fund_gui.count] = (_game_mode == GM_EDITOR) || CheckIfCallBackAllowsAvailability(ind, IACT_USERCREATION); belugas@7776: _fund_gui.count++; belugas@7714: } belugas@7714: } belugas@7714: belugas@7714: if (_patches.raw_industry_construction != 0 && _game_mode != GM_EDITOR) { rubidium@7815: for (ind = 0; ind < NUM_INDUSTRYTYPES; ind++) { belugas@7714: indsp = GetIndustrySpec(ind); belugas@7714: if (indsp->enabled && indsp->IsRawIndustry()) { belugas@7776: _fund_gui.index[_fund_gui.count] = ind; glx@8143: _fund_gui.enabled[_fund_gui.count] = (_game_mode == GM_EDITOR) || CheckIfCallBackAllowsAvailability(ind, IACT_USERCREATION); belugas@7776: _fund_gui.count++; belugas@7714: } belugas@7714: } belugas@7714: } belugas@7779: belugas@7778: /* first indutry type is selected. belugas@7778: * I'll be damned if there are none available ;) */ belugas@7778: WP(w, fnd_d).index = 0; belugas@7778: WP(w, fnd_d).select = _fund_gui.index[0]; belugas@7779: WP(w, fnd_d).callback_timer = DAY_TICKS; belugas@7714: } break; belugas@7714: belugas@7714: case WE_PAINT: { belugas@7778: const IndustrySpec *indsp = (WP(w, fnd_d).select == INVALID_INDUSTRYTYPE) ? NULL : GetIndustrySpec(WP(w, fnd_d).select); belugas@7714: StringID str = STR_4827_REQUIRES; belugas@7714: int x_str = w->widget[DYNA_INDU_INFOPANEL].left + 3; belugas@7714: int y_str = w->widget[DYNA_INDU_INFOPANEL].top + 3; belugas@7714: const Widget *wi = &w->widget[DYNA_INDU_INFOPANEL]; belugas@7714: int max_width = wi->right - wi->left - 4; belugas@7714: belugas@7776: /* Raw industries might be prospected. Show this fact by changing the string belugas@7776: * In Editor, you just build, while ingame, or you fund or you prospect */ belugas@7714: if (_game_mode == GM_EDITOR) { belugas@7779: /* We've chosen many random industries but no industries have been specified */ belugas@7785: if (indsp == NULL) _fund_gui.enabled[WP(w, fnd_d).index] = _opt.diff.number_industries != 0; belugas@7714: w->widget[DYNA_INDU_FUND_WIDGET].data = STR_BUILD_NEW_INDUSTRY; belugas@7714: } else { belugas@7714: w->widget[DYNA_INDU_FUND_WIDGET].data = (_patches.raw_industry_construction == 2 && indsp->IsRawIndustry()) ? STR_PROSPECT_NEW_INDUSTRY : STR_FUND_NEW_INDUSTRY; belugas@7714: } belugas@7779: SetWindowWidgetDisabledState(w, DYNA_INDU_FUND_WIDGET, !_fund_gui.enabled[WP(w, fnd_d).index]); belugas@7714: belugas@7776: SetVScrollCount(w, _fund_gui.count); belugas@7714: belugas@7714: DrawWindowWidgets(w); belugas@7714: belugas@7714: /* and now with the matrix painting */ belugas@7776: for (byte i = 0; i < w->vscroll.cap && ((i + w->vscroll.pos) < _fund_gui.count); i++) { belugas@7714: int offset = i * 13; belugas@7714: int x = 3; belugas@7714: int y = 16; belugas@7778: bool selected = WP(w, fnd_d).index == i + w->vscroll.pos; belugas@7714: belugas@7776: if (_fund_gui.index[i + w->vscroll.pos] == INVALID_INDUSTRYTYPE) { belugas@7714: DrawString(21, y + offset, STR_MANY_RANDOM_INDUSTRIES, selected ? 12 : 6); belugas@7714: continue; belugas@7714: } belugas@7776: const IndustrySpec *indsp = GetIndustrySpec(_fund_gui.index[i + w->vscroll.pos]); belugas@7714: belugas@7714: /* Draw the name of the industry in white is selected, otherwise, in orange */ belugas@7714: DrawString(20, y + offset, indsp->name, selected ? 12 : 6); belugas@7714: GfxFillRect(x, y + 1 + offset, x + 10, y + 7 + offset, selected ? 15 : 0); belugas@7714: GfxFillRect(x + 1, y + 2 + offset, x + 9, y + 6 + offset, indsp->map_colour); belugas@7714: } belugas@7714: belugas@7778: if (WP(w, fnd_d).select == INVALID_INDUSTRYTYPE) { belugas@7714: DrawStringMultiLine(x_str, y_str, STR_RANDOM_INDUSTRIES_TIP, max_width, wi->bottom - wi->top - 40); belugas@7714: break; belugas@7714: } belugas@7714: belugas@7714: if (_game_mode != GM_EDITOR) { belugas@7714: SetDParam(0, indsp->GetConstructionCost()); belugas@7714: DrawStringTruncated(x_str, y_str, STR_482F_COST, 0, max_width); belugas@7714: y_str += 11; belugas@7714: } belugas@7714: belugas@7714: /* Draw the accepted cargos, if any. Otherwhise, will print "Nothing" */ belugas@7714: if (indsp->accepts_cargo[0] != CT_INVALID) { belugas@7714: SetDParam(0, GetCargo(indsp->accepts_cargo[0])->name); belugas@7714: if (indsp->accepts_cargo[1] != CT_INVALID) { belugas@7714: SetDParam(1, GetCargo(indsp->accepts_cargo[1])->name); belugas@7714: str = STR_4828_REQUIRES; belugas@7714: if (indsp->accepts_cargo[2] != CT_INVALID) { belugas@7714: SetDParam(2, GetCargo(indsp->accepts_cargo[2])->name); belugas@7714: str = STR_4829_REQUIRES; belugas@7714: } belugas@7714: } belugas@7714: } else { belugas@7714: SetDParam(0, STR_00D0_NOTHING); belugas@7714: } belugas@7714: DrawStringTruncated(x_str, y_str, str, 0, max_width); belugas@7714: belugas@7714: y_str += 11; belugas@7714: /* Draw the produced cargos, if any. Otherwhise, will print "Nothing" */ belugas@7714: str = STR_4827_PRODUCES; belugas@7714: if (indsp->produced_cargo[0] != CT_INVALID) { belugas@7714: SetDParam(0, GetCargo(indsp->produced_cargo[0])->name); belugas@7714: if (indsp->produced_cargo[1] != CT_INVALID) { belugas@7714: SetDParam(1, GetCargo(indsp->produced_cargo[1])->name); belugas@7714: str = STR_4828_PRODUCES; belugas@7714: } belugas@7714: } else { belugas@7714: SetDParam(0, STR_00D0_NOTHING); belugas@7714: } belugas@7714: DrawStringTruncated(x_str, y_str, str, 0, max_width); belugas@7714: belugas@7714: /* Get the additional purchase info text, if it has not already been */ belugas@7778: if (_fund_gui.text[WP(w, fnd_d).index] == STR_NULL) { // Have i been called already? belugas@7714: if (HASBIT(indsp->callback_flags, CBM_IND_FUND_MORE_TEXT)) { // No. Can it be called? belugas@7778: uint16 callback_res = GetIndustryCallback(CBID_INDUSTRY_FUND_MORE_TEXT, 0, 0, NULL, WP(w, fnd_d).select, INVALID_TILE); belugas@7714: if (callback_res != CALLBACK_FAILED) { // Did it failed? belugas@7714: StringID newtxt = GetGRFStringID(indsp->grf_prop.grffile->grfid, 0xD000 + callback_res); // No. here's the new string belugas@7778: _fund_gui.text[WP(w, fnd_d).index] = newtxt; // Store it for further usage belugas@7714: } belugas@7714: } belugas@7714: } belugas@7714: belugas@7714: y_str += 11; belugas@7714: /* Draw the Additional purchase text, provided by newgrf callback, if any. belugas@7714: * Otherwhise, will print Nothing */ belugas@7778: str = _fund_gui.text[WP(w, fnd_d).index]; belugas@7776: if (str != STR_NULL && str != STR_UNDEFINED) { belugas@7776: SetDParam(0, str); belugas@7776: DrawStringMultiLine(x_str, y_str, STR_JUST_STRING, max_width, wi->bottom - wi->top - 40); belugas@7714: } belugas@7714: } break; belugas@7714: belugas@7714: case WE_CLICK: belugas@7714: switch (e->we.click.widget) { belugas@7714: case DYNA_INDU_MATRIX_WIDGET: { belugas@7778: const IndustrySpec *indsp; belugas@7714: int y = (e->we.click.pt.y - w->widget[DYNA_INDU_MATRIX_WIDGET].top) / 13 + w->vscroll.pos ; belugas@7714: belugas@7776: if (y >= 0 && y < _fund_gui.count) { // Is it within the boundaries of available data? belugas@7778: WP(w, fnd_d).index = y; belugas@7778: WP(w, fnd_d).select = _fund_gui.index[WP(w, fnd_d).index]; belugas@7778: indsp = (WP(w, fnd_d).select == INVALID_INDUSTRYTYPE) ? NULL : GetIndustrySpec(WP(w, fnd_d).select); belugas@7714: belugas@7714: SetWindowDirty(w); belugas@7776: belugas@7778: if ((_game_mode != GM_EDITOR && _patches.raw_industry_construction == 2 && indsp != NULL && indsp->IsRawIndustry()) || belugas@7778: WP(w, fnd_d).select == INVALID_INDUSTRYTYPE) { belugas@7714: /* Reset the button state if going to prospecting or "build many industries" */ belugas@7714: RaiseWindowButtons(w); belugas@7714: ResetObjectToPlace(); belugas@7714: } belugas@7714: } belugas@7714: } break; belugas@7714: belugas@7714: case DYNA_INDU_FUND_WIDGET: { belugas@7778: if (WP(w, fnd_d).select == INVALID_INDUSTRYTYPE) { belugas@7714: HandleButtonClick(w, DYNA_INDU_FUND_WIDGET); belugas@7714: belugas@7714: if (GetNumTowns() == 0) { belugas@7714: ShowErrorMessage(STR_0286_MUST_BUILD_TOWN_FIRST, STR_CAN_T_GENERATE_INDUSTRIES, 0, 0); belugas@7714: } else { belugas@7714: extern void GenerateIndustries(); belugas@7714: _generating_world = true; belugas@7714: GenerateIndustries(); belugas@7714: _generating_world = false; belugas@7714: } belugas@7778: } else if (_game_mode != GM_EDITOR && _patches.raw_industry_construction == 2 && GetIndustrySpec(WP(w, fnd_d).select)->IsRawIndustry()) { belugas@7778: DoCommandP(0, WP(w, fnd_d).select, 0, NULL, CMD_BUILD_INDUSTRY | CMD_MSG(STR_4830_CAN_T_CONSTRUCT_THIS_INDUSTRY)); belugas@7714: HandleButtonClick(w, DYNA_INDU_FUND_WIDGET); belugas@7778: } else { belugas@7778: HandlePlacePushButton(w, DYNA_INDU_FUND_WIDGET, SPR_CURSOR_INDUSTRY, 1, NULL); belugas@7714: } belugas@7767: } break; belugas@7767: } belugas@7767: break; belugas@7714: belugas@7767: case WE_RESIZE: { belugas@7776: /* Adjust the number of items in the matrix depending of the rezise */ belugas@7767: w->vscroll.cap += e->we.sizing.diff.y / (int)w->resize.step_height; belugas@7767: w->widget[DYNA_INDU_MATRIX_WIDGET].data = (w->vscroll.cap << 8) + 1; belugas@7767: } break; belugas@7714: belugas@7767: case WE_PLACE_OBJ: { belugas@7776: /* We do not need to protect ourselves against "Random Many Industries" in this mode */ belugas@7778: const IndustrySpec *indsp = GetIndustrySpec(WP(w, fnd_d).select); belugas@7767: belugas@7767: if (_game_mode == GM_EDITOR) { belugas@7767: /* Show error if no town exists at all */ belugas@7767: if (GetNumTowns() == 0) { belugas@7778: SetDParam(0, indsp->name); belugas@7767: ShowErrorMessage(STR_0286_MUST_BUILD_TOWN_FIRST, STR_0285_CAN_T_BUILD_HERE, e->we.place.pt.x, e->we.place.pt.y); belugas@7767: return; belugas@7767: } belugas@7767: belugas@7767: _current_player = OWNER_NONE; belugas@7767: _generating_world = true; belugas@7767: _ignore_restrictions = true; belugas@7778: if (!TryBuildIndustry(e->we.place.tile, WP(w, fnd_d).select)) { belugas@7778: SetDParam(0, indsp->name); belugas@7767: ShowErrorMessage(_error_message, STR_0285_CAN_T_BUILD_HERE, e->we.place.pt.x, e->we.place.pt.y); belugas@7767: } belugas@7778: belugas@7767: _ignore_restrictions = false; belugas@7767: _generating_world = false; belugas@7778: } else DoCommandP(e->we.place.tile, WP(w, fnd_d).select, 0, NULL, CMD_BUILD_INDUSTRY | CMD_MSG(STR_4830_CAN_T_CONSTRUCT_THIS_INDUSTRY)); belugas@7778: belugas@7778: /* Whatever the outcome of the actions, just reset the cursor and the system */ belugas@7778: ResetObjectToPlace(); belugas@7767: } break; truelight@0: belugas@7779: case WE_TICK: belugas@7779: if (!WP(w, fnd_d).timer_enabled) break; belugas@7779: if (--WP(w, fnd_d).callback_timer == 0) { belugas@7779: /* We have just passed another day. belugas@7779: * See if we need to update availability of currently selected industry */ belugas@7779: WP(w, fnd_d).callback_timer = DAY_TICKS; //restart counter belugas@7779: belugas@7779: const IndustrySpec *indsp = GetIndustrySpec(WP(w, fnd_d).select); belugas@7779: belugas@7779: if (indsp->enabled) { belugas@7779: bool call_back_result = CheckIfCallBackAllowsAvailability(WP(w, fnd_d).select, IACT_USERCREATION); belugas@7779: belugas@7779: /* Only if result does match the previous state would it require a redraw. */ belugas@7779: if (call_back_result != _fund_gui.enabled[WP(w, fnd_d).index]) { belugas@7779: _fund_gui.enabled[WP(w, fnd_d).index] = call_back_result; belugas@7779: SetWindowDirty(w); belugas@7779: } belugas@7779: } belugas@7779: } belugas@7767: break; truelight@193: belugas@7767: case WE_TIMEOUT: belugas@7778: case WE_ABORT_PLACE_OBJ: belugas@7778: RaiseWindowButtons(w); belugas@7767: break; truelight@0: } truelight@0: } truelight@0: belugas@7714: static const Widget _build_dynamic_industry_widgets[] = { belugas@7714: { WWT_CLOSEBOX, RESIZE_NONE, 7, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, belugas@7714: { WWT_CAPTION, RESIZE_RIGHT, 7, 11, 169, 0, 13, STR_0314_FUND_NEW_INDUSTRY, STR_018C_WINDOW_TITLE_DRAG_THIS}, belugas@7714: { WWT_MATRIX, RESIZE_RB, 7, 0, 157, 14, 118, 0x801, STR_INDUSTRY_SELECTION_HINT}, belugas@7714: { WWT_SCROLLBAR, RESIZE_LRB, 7, 158, 169, 14, 118, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, belugas@7714: { WWT_PANEL, RESIZE_RTB, 7, 0, 169, 119, 199, 0x0, STR_NULL}, belugas@7714: { WWT_TEXTBTN, RESIZE_RTB, 7, 0, 157, 200, 211, STR_FUND_NEW_INDUSTRY, STR_NULL}, belugas@7714: { WWT_RESIZEBOX, RESIZE_LRTB, 7, 158, 169, 200, 211, 0x0, STR_RESIZE_BUTTON}, darkvater@176: { WIDGETS_END}, truelight@0: }; truelight@0: belugas@7714: static const WindowDesc _build_industry_dynamic_desc = { rubidium@7837: WDP_AUTO, WDP_AUTO, 170, 212, 170, 212, rubidium@6144: WC_BUILD_INDUSTRY, WC_NONE, belugas@7714: WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_RESIZABLE, belugas@7714: _build_dynamic_industry_widgets, belugas@7714: BuildDynamicIndustryWndProc, truelight@0: }; truelight@0: rubidium@6573: void ShowBuildIndustryWindow() truelight@193: { belugas@7714: if (_game_mode != GM_EDITOR && !IsValidPlayer(_current_player)) return; belugas@7714: AllocateWindowDescFront(&_build_industry_dynamic_desc, 0); truelight@0: } truelight@0: belugas@7714: static void UpdateIndustryProduction(Industry *i); belugas@7714: rubidium@7780: static inline bool isProductionMinimum(const Industry *i, int pt) rubidium@7780: { miham@4211: return i->production_rate[pt] == 1; miham@4211: } miham@4211: rubidium@7780: static inline bool isProductionMaximum(const Industry *i, int pt) rubidium@7780: { miham@4211: return i->production_rate[pt] == 255; miham@4211: } miham@4211: Darkvater@4194: static inline bool IsProductionAlterable(const Industry *i) Darkvater@4194: { Darkvater@4194: return ((_game_mode == GM_EDITOR || _cheats.setup_prod.value) && glx@8141: (i->accepts_cargo[0] == CT_INVALID || i->accepts_cargo[0] == CT_VALUABLES)); Darkvater@4194: } Darkvater@4194: rubidium@7780: /** Information to store about the industry window */ rubidium@7780: struct indview_d : public vp_d { rubidium@7780: byte editbox_line; ///< The line clicked to open the edit box rubidium@7780: byte clicked_line; ///< The line of the button that has been clicked rubidium@7780: byte clicked_button; ///< The button that has been clicked (to raise) rubidium@7780: byte production_offset_y; ///< The offset of the production texts/buttons rubidium@7780: }; rubidium@7780: assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(indview_d)); rubidium@7780: rubidium@7780: truelight@0: static void IndustryViewWndProc(Window *w, WindowEvent *e) truelight@0: { tron@2951: switch (e->event) { rubidium@7691: case WE_CREATE: { rubidium@7691: /* Count the number of lines that we need to resize the GUI with */ glx@8141: const Industry *i = GetIndustry(w->window_number); glx@8141: const IndustrySpec *ind = GetIndustrySpec(i->type); rubidium@7691: int lines = -3; rubidium@7691: rubidium@7691: if (HASBIT(ind->callback_flags, CBM_IND_PRODUCTION_CARGO_ARRIVAL) || HASBIT(ind->callback_flags, CBM_IND_PRODUCTION_256_TICKS)) { glx@8141: for (uint j = 0; j < lengthof(i->accepts_cargo) && i->accepts_cargo[j] != CT_INVALID; j++) { rubidium@7691: if (j == 0) lines++; rubidium@7691: lines++; rubidium@7691: } glx@8141: } else if (i->accepts_cargo[0] != CT_INVALID) { rubidium@7691: lines++; rubidium@7691: } rubidium@7691: glx@8141: for (uint j = 0; j < lengthof(i->produced_cargo) && i->produced_cargo[j] != CT_INVALID; j++) { rubidium@7691: if (j == 0) { glx@8141: if (i->accepts_cargo[0] != CT_INVALID) lines++; rubidium@7691: lines++; rubidium@7691: } rubidium@7691: lines++; rubidium@7691: } rubidium@7691: rubidium@7691: if (HASBIT(ind->callback_flags, CBM_IND_WINDOW_MORE_TEXT)) lines += 2; rubidium@7691: rubidium@7691: for (uint j = 5; j <= 7; j++) { rubidium@7691: if (j != 5) w->widget[j].top += lines * 10; rubidium@7691: w->widget[j].bottom += lines * 10; rubidium@7691: } rubidium@7691: w->height += lines * 10; rubidium@7691: } break; rubidium@7691: miham@1004: case WE_PAINT: { rubidium@7691: Industry *i = GetIndustry(w->window_number); belugas@7132: const IndustrySpec *ind = GetIndustrySpec(i->type); rubidium@7691: int y = 111; tron@2557: ludde@2070: SetDParam(0, w->window_number); truelight@0: DrawWindowWidgets(w); truelight@0: rubidium@7691: if (HASBIT(ind->callback_flags, CBM_IND_PRODUCTION_CARGO_ARRIVAL) || HASBIT(ind->callback_flags, CBM_IND_PRODUCTION_256_TICKS)) { glx@8141: for (uint j = 0; j < lengthof(i->accepts_cargo) && i->accepts_cargo[j] != CT_INVALID; j++) { rubidium@7691: if (j == 0) { rubidium@7691: DrawString(2, y, STR_INDUSTRY_WINDOW_WAITING_FOR_PROCESSING, 0); rubidium@7691: y += 10; rubidium@7691: } glx@8141: SetDParam(0, i->accepts_cargo[j]); rubidium@7691: SetDParam(1, i->incoming_cargo_waiting[j]); rubidium@7691: DrawString(4, y, STR_INDUSTRY_WINDOW_WAITING_STOCKPILE_CARGO, 0); rubidium@7691: y += 10; rubidium@7691: } glx@8141: } else if (i->accepts_cargo[0] != CT_INVALID) { tron@2989: StringID str; tron@2989: glx@8141: SetDParam(0, GetCargo(i->accepts_cargo[0])->name); truelight@0: str = STR_4827_REQUIRES; glx@8141: if (i->accepts_cargo[1] != CT_INVALID) { glx@8141: SetDParam(1, GetCargo(i->accepts_cargo[1])->name); tron@2989: str = STR_4828_REQUIRES; glx@8141: if (i->accepts_cargo[2] != CT_INVALID) { glx@8141: SetDParam(2, GetCargo(i->accepts_cargo[2])->name); tron@2989: str = STR_4829_REQUIRES; truelight@0: } truelight@0: } rubidium@7691: DrawString(2, y, str, 0); rubidium@7691: y += 10; truelight@0: } truelight@193: glx@8141: for (uint j = 0; j < lengthof(i->produced_cargo) && i->produced_cargo[j] != CT_INVALID; j++) { rubidium@7691: if (j == 0) { glx@8141: if (i->accepts_cargo[0] != CT_INVALID) y += 10; rubidium@7691: DrawString(2, y, STR_482A_PRODUCTION_LAST_MONTH, 0); rubidium@7691: y += 10; rubidium@7780: WP(w, indview_d).production_offset_y = y; rubidium@7691: } truelight@0: glx@8141: SetDParam(0, i->produced_cargo[j]); rubidium@7691: SetDParam(1, i->last_month_production[j]); ludde@2063: rubidium@7691: SetDParam(2, i->last_month_pct_transported[j] * 100 >> 8); rubidium@7691: DrawString(4 + (IsProductionAlterable(i) ? 30 : 0), y, STR_482B_TRANSPORTED, 0); belugas@6527: /* Let's put out those buttons.. */ miham@4211: if (IsProductionAlterable(i)) { rubidium@7780: DrawArrowButtons(5, y, 3, (WP(w, indview_d).clicked_line == j + 1) ? WP(w, indview_d).clicked_button : 0, rubidium@7691: !isProductionMinimum(i, j), !isProductionMaximum(i, j)); miham@4211: } rubidium@7691: y += 10; rubidium@7691: } truelight@0: rubidium@7691: /* Get the extra message for the GUI */ rubidium@7691: if (HASBIT(ind->callback_flags, CBM_IND_WINDOW_MORE_TEXT)) { rubidium@7691: uint16 callback_res = GetIndustryCallback(CBID_INDUSTRY_WINDOW_MORE_TEXT, 0, 0, i, i->type, i->xy); rubidium@7691: if (callback_res != CALLBACK_FAILED) { rubidium@7691: StringID message = GetGRFStringID(ind->grf_prop.grffile->grfid, 0xD000 + callback_res); rubidium@7691: if (message != STR_NULL && message != STR_UNDEFINED) { rubidium@7691: y += 10; rubidium@8112: rubidium@8112: PrepareTextRefStackUsage(); rubidium@7691: DrawString(2, y, message, 0); rubidium@8112: StopTextRefStackUsage(); miham@4211: } truelight@0: } truelight@0: } truelight@0: truelight@0: DrawWindowViewport(w); rubidium@7691: } break; truelight@0: miham@1004: case WE_CLICK: { miham@1004: Industry *i; miham@1004: belugas@4634: switch (e->we.click.widget) { miham@1004: case 5: { Darkvater@4193: int line, x; miham@1004: miham@1004: i = GetIndustry(w->window_number); miham@1004: belugas@6527: /* We should work if needed.. */ Darkvater@4194: if (!IsProductionAlterable(i)) return; belugas@4634: x = e->we.click.pt.x; rubidium@7780: line = (e->we.click.pt.y - WP(w, indview_d).production_offset_y) / 10; glx@8141: if (e->we.click.pt.y >= WP(w, indview_d).production_offset_y && IS_INT_INSIDE(line, 0, 2) && i->produced_cargo[line] != CT_INVALID) { tron@1019: if (IS_INT_INSIDE(x, 5, 25) ) { Darkvater@4193: /* Clicked buttons, decrease or increase production */ tron@1019: if (x < 15) { miham@4211: if (isProductionMinimum(i, line)) return; celestar@5852: i->production_rate[line] = max(i->production_rate[line] / 2, 1); miham@1004: } else { miham@4211: if (isProductionMaximum(i, line)) return; Darkvater@4193: i->production_rate[line] = minu(i->production_rate[line] * 2, 255); miham@1004: } Darkvater@4193: miham@1004: UpdateIndustryProduction(i); miham@1004: SetWindowDirty(w); miham@1004: w->flags4 |= 5 << WF_TIMEOUT_SHL; rubidium@7780: WP(w, indview_d).clicked_line = line + 1; rubidium@7780: WP(w, indview_d).clicked_button = (x < 15 ? 1 : 2); miham@1004: } else if (IS_INT_INSIDE(x, 34, 160)) { belugas@6527: /* clicked the text */ rubidium@7780: WP(w, indview_d).editbox_line = line; miham@1004: SetDParam(0, i->production_rate[line] * 8); Darkvater@5682: ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_CONFIG_GAME_PRODUCTION, 10, 100, w, CS_ALPHANUMERAL); miham@1004: } tron@1019: } Darkvater@4193: } break; darkvater@758: case 6: truelight@919: i = GetIndustry(w->window_number); tron@1981: ScrollMainWindowToTile(i->xy + TileDiffXY(1, 1)); rubidium@6988: } break; Darkvater@4193: miham@1004: } truelight@0: break; miham@1004: case WE_TIMEOUT: rubidium@7780: WP(w, indview_d).clicked_line = 0; rubidium@7780: WP(w, indview_d).clicked_button = 0; miham@1004: SetWindowDirty(w); miham@1004: break; miham@1004: miham@1004: case WE_ON_EDIT_TEXT: belugas@4634: if (e->we.edittext.str[0] != '\0') { tron@3017: Industry* i = GetIndustry(w->window_number); rubidium@7780: int line = WP(w, indview_d).editbox_line; miham@1004: belugas@4634: i->production_rate[line] = clampu(atoi(e->we.edittext.str), 0, 255); miham@1004: UpdateIndustryProduction(i); miham@1004: SetWindowDirty(w); miham@1004: } truelight@0: } truelight@0: } truelight@0: miham@1004: static void UpdateIndustryProduction(Industry *i) miham@1004: { glx@8141: for (byte j = 0; j < lengthof(i->produced_cargo); j++) { glx@8141: if (i->produced_cargo[j] != CT_INVALID) { rubidium@7315: i->last_month_production[j] = 8 * i->production_rate[j]; belugas@7132: } belugas@7132: } miham@1004: } miham@1004: truelight@0: static const Widget _industry_view_widgets[] = { rubidium@4344: { WWT_CLOSEBOX, RESIZE_NONE, 9, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, rubidium@4344: { WWT_CAPTION, RESIZE_NONE, 9, 11, 247, 0, 13, STR_4801, STR_018C_WINDOW_TITLE_DRAG_THIS}, rubidium@4344: { WWT_STICKYBOX, RESIZE_NONE, 9, 248, 259, 0, 13, 0x0, STR_STICKY_BUTTON}, Darkvater@4938: { WWT_PANEL, RESIZE_NONE, 9, 0, 259, 14, 105, 0x0, STR_NULL}, Darkvater@4939: { WWT_INSET, RESIZE_NONE, 9, 2, 257, 16, 103, 0x0, STR_NULL}, Darkvater@4938: { WWT_PANEL, RESIZE_NONE, 9, 0, 259, 106, 147, 0x0, STR_NULL}, rubidium@4344: { WWT_PUSHTXTBTN, RESIZE_NONE, 9, 0, 129, 148, 159, STR_00E4_LOCATION, STR_482C_CENTER_THE_MAIN_VIEW_ON}, Darkvater@4938: { WWT_PANEL, RESIZE_NONE, 9, 130, 259, 148, 159, 0x0, STR_NULL}, darkvater@176: { WIDGETS_END}, truelight@0: }; truelight@0: truelight@0: static const WindowDesc _industry_view_desc = { rubidium@7837: WDP_AUTO, WDP_AUTO, 260, 160, 260, 160, rubidium@6144: WC_INDUSTRY_VIEW, WC_NONE, darkvater@758: WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON, truelight@0: _industry_view_widgets, truelight@0: IndustryViewWndProc truelight@0: }; truelight@0: truelight@0: void ShowIndustryViewWindow(int industry) truelight@0: { belugas@4171: Window *w = AllocateWindowDescFront(&_industry_view_desc, industry); truelight@0: tron@2989: if (w != NULL) { truelight@0: w->flags4 |= WF_DISABLE_VP_SCROLL; rubidium@7780: WP(w, indview_d).editbox_line = 0; rubidium@7780: WP(w, indview_d).clicked_line = 0; rubidium@7780: WP(w, indview_d).clicked_button = 0; truelight@7120: AssignWindowViewport(w, 3, 17, 0xFE, 0x56, GetIndustry(w->window_number)->xy + TileDiffXY(1, 1), ZOOM_LVL_INDUSTRY); truelight@0: } truelight@0: } truelight@0: belugas@7774: enum { belugas@7774: DIRECTORY_INDU_SORTBYNAME = 3, belugas@7774: DIRECTORY_INDU_SORTBYTYPE, belugas@7774: DIRECTORY_INDU_SORTBYPROD, belugas@7774: DIRECTORY_INDU_SORTBYTRANSPORT, belugas@7774: DIRECTORY_INDU_SHOWINDU = 8, belugas@7774: }; belugas@7774: truelight@0: static const Widget _industry_directory_widgets[] = { rubidium@4344: { WWT_CLOSEBOX, RESIZE_NONE, 13, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, rubidium@4344: { WWT_CAPTION, RESIZE_NONE, 13, 11, 495, 0, 13, STR_INDUSTRYDIR_CAPTION, STR_018C_WINDOW_TITLE_DRAG_THIS}, rubidium@4344: { WWT_STICKYBOX, RESIZE_NONE, 13, 496, 507, 0, 13, 0x0, STR_STICKY_BUTTON}, rubidium@4344: { WWT_PUSHTXTBTN, RESIZE_NONE, 13, 0, 100, 14, 25, STR_SORT_BY_NAME, STR_SORT_ORDER_TIP}, rubidium@4344: { WWT_PUSHTXTBTN, RESIZE_NONE, 13, 101, 200, 14, 25, STR_SORT_BY_TYPE, STR_SORT_ORDER_TIP}, rubidium@4344: { WWT_PUSHTXTBTN, RESIZE_NONE, 13, 201, 300, 14, 25, STR_SORT_BY_PRODUCTION, STR_SORT_ORDER_TIP}, rubidium@4344: { WWT_PUSHTXTBTN, RESIZE_NONE, 13, 301, 400, 14, 25, STR_SORT_BY_TRANSPORTED, STR_SORT_ORDER_TIP}, rubidium@4344: { WWT_PANEL, RESIZE_NONE, 13, 401, 495, 14, 25, 0x0, STR_NULL}, Darkvater@4938: { WWT_PANEL, RESIZE_BOTTOM, 13, 0, 495, 26, 189, 0x0, STR_200A_TOWN_NAMES_CLICK_ON_NAME}, rubidium@4344: { WWT_SCROLLBAR, RESIZE_BOTTOM, 13, 496, 507, 14, 177, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, rubidium@4344: { WWT_RESIZEBOX, RESIZE_TB, 13, 496, 507, 178, 189, 0x0, STR_RESIZE_BUTTON}, darkvater@176: { WIDGETS_END}, truelight@0: }; truelight@0: truelight@0: static uint _num_industry_sort; truelight@0: truelight@0: static char _bufcache[96]; tron@4277: static const Industry* _last_industry; truelight@0: truelight@0: static byte _industry_sort_order; truelight@0: darkvater@164: static int CDECL GeneralIndustrySorter(const void *a, const void *b) truelight@0: { tron@4277: const Industry* i = *(const Industry**)a; tron@4277: const Industry* j = *(const Industry**)b; tron@4282: int r; truelight@0: truelight@0: switch (_industry_sort_order >> 1) { tron@4282: default: NOT_REACHED(); tron@4282: case 0: /* Sort by Name (handled later) */ truelight@0: r = 0; tron@4282: break; tron@4282: tron@4282: case 1: /* Sort by Type */ tron@4282: r = i->type - j->type; tron@4282: break; tron@4282: tron@4282: case 2: /* Sort by Production */ glx@8141: if (i->produced_cargo[0] == CT_INVALID) { glx@8141: r = (j->produced_cargo[0] == CT_INVALID ? 0 : -1); tron@4282: } else { glx@8141: if (j->produced_cargo[0] == CT_INVALID) { tron@4282: r = 1; tron@4282: } else { tron@4282: r = rubidium@7315: (i->last_month_production[0] + i->last_month_production[1]) - rubidium@7315: (j->last_month_production[0] + j->last_month_production[1]); tron@4282: } tron@4282: } tron@4282: break; tron@4282: tron@4282: case 3: /* Sort by transported fraction */ glx@8141: if (i->produced_cargo[0] == CT_INVALID) { glx@8141: r = (j->produced_cargo[0] == CT_INVALID ? 0 : -1); tron@4282: } else { glx@8141: if (j->produced_cargo[0] == CT_INVALID) { tron@4282: r = 1; tron@4282: } else { tron@4282: int pi; tron@4282: int pj; tron@4282: rubidium@7315: pi = i->last_month_pct_transported[0] * 100 >> 8; glx@8141: if (i->produced_cargo[1] != CT_INVALID) { rubidium@7315: int p = i->last_month_pct_transported[1] * 100 >> 8; tron@4282: if (p < pi) pi = p; tron@4282: } tron@4282: rubidium@7315: pj = j->last_month_pct_transported[0] * 100 >> 8; glx@8141: if (j->produced_cargo[1] != CT_INVALID) { rubidium@7315: int p = j->last_month_pct_transported[1] * 100 >> 8; tron@4282: if (p < pj) pj = p; tron@4282: } tron@4282: tron@4282: r = pi - pj; tron@4282: } tron@4282: } tron@4282: break; truelight@0: } truelight@0: belugas@6527: /* default to string sorting if they are otherwise equal */ truelight@0: if (r == 0) { tron@4282: char buf1[96]; tron@4282: ludde@2070: SetDParam(0, i->town->index); Darkvater@4912: GetString(buf1, STR_TOWN, lastof(buf1)); truelight@0: tron@4277: if (j != _last_industry) { tron@4277: _last_industry = j; ludde@2070: SetDParam(0, j->town->index); Darkvater@4912: GetString(_bufcache, STR_TOWN, lastof(_bufcache)); truelight@0: } truelight@0: r = strcmp(buf1, _bufcache); truelight@0: } truelight@193: truelight@193: if (_industry_sort_order & 1) r = -r; truelight@0: return r; truelight@0: } truelight@0: rubidium@6526: /** rubidium@6526: * Makes a sorted industry list. rubidium@6526: * When there are no industries, the list has to be made. This so when one rubidium@6526: * starts a new game without industries after playing a game with industries rubidium@6526: * the list is not populated with invalid industries from the previous game. rubidium@6526: */ rubidium@6573: static void MakeSortedIndustryList() truelight@0: { tron@4277: const Industry* i; truelight@919: int n = 0; truelight@919: truelight@919: /* Create array for sorting */ KUDr@5860: _industry_sort = ReallocT(_industry_sort, GetMaxIndustryIndex() + 1); Darkvater@5568: if (_industry_sort == NULL) error("Could not allocate memory for the industry-sorting-list"); truelight@0: rubidium@6526: /* Don't attempt a sort if there are no industries */ rubidium@6526: if (GetNumIndustries() != 0) { rubidium@6526: FOR_ALL_INDUSTRIES(i) _industry_sort[n++] = i; rubidium@6526: qsort((void*)_industry_sort, n, sizeof(_industry_sort[0]), GeneralIndustrySorter); rubidium@6526: } truelight@4346: truelight@0: _num_industry_sort = n; tron@4277: _last_industry = NULL; // used for "cache" truelight@0: Darkvater@5568: DEBUG(misc, 3, "Resorting industries list"); truelight@0: } truelight@0: truelight@0: truelight@0: static void IndustryDirectoryWndProc(Window *w, WindowEvent *e) truelight@0: { tron@2951: switch (e->event) { truelight@0: case WE_PAINT: { truelight@0: int n; truelight@0: uint p; truelight@0: static const uint16 _indicator_positions[4] = {88, 187, 284, 387}; truelight@0: truelight@0: if (_industry_sort_dirty) { truelight@0: _industry_sort_dirty = false; truelight@0: MakeSortedIndustryList(); truelight@0: } truelight@0: bjarni@1666: SetVScrollCount(w, _num_industry_sort); truelight@0: truelight@0: DrawWindowWidgets(w); rubidium@6987: DoDrawString(_industry_sort_order & 1 ? DOWNARROW : UPARROW, _indicator_positions[_industry_sort_order >> 1], 15, 0x10); truelight@0: truelight@0: p = w->vscroll.pos; truelight@0: n = 0; truelight@0: truelight@0: while (p < _num_industry_sort) { tron@4277: const Industry* i = _industry_sort[p]; tron@2989: ludde@2070: SetDParam(0, i->index); glx@8141: if (i->produced_cargo[0] != CT_INVALID) { glx@8141: SetDParam(1, i->produced_cargo[0]); rubidium@7315: SetDParam(2, i->last_month_production[0]); truelight@193: glx@8141: if (i->produced_cargo[1] != CT_INVALID) { glx@8141: SetDParam(3, i->produced_cargo[1]); rubidium@7315: SetDParam(4, i->last_month_production[1]); rubidium@7315: SetDParam(5, i->last_month_pct_transported[0] * 100 >> 8); rubidium@7315: SetDParam(6, i->last_month_pct_transported[1] * 100 >> 8); rubidium@6987: DrawString(4, 28 + n * 10, STR_INDUSTRYDIR_ITEM_TWO, 0); truelight@0: } else { rubidium@7315: SetDParam(3, i->last_month_pct_transported[0] * 100 >> 8); rubidium@6987: DrawString(4, 28 + n * 10, STR_INDUSTRYDIR_ITEM, 0); truelight@0: } truelight@0: } else { rubidium@6987: DrawString(4, 28 + n * 10, STR_INDUSTRYDIR_ITEM_NOPROD, 0); truelight@0: } truelight@0: p++; tron@2951: if (++n == w->vscroll.cap) break; truelight@0: } truelight@0: } break; truelight@0: truelight@0: case WE_CLICK: belugas@4634: switch (e->we.click.widget) { belugas@7774: case DIRECTORY_INDU_SORTBYNAME: { belugas@7774: _industry_sort_order = _industry_sort_order == 0 ? 1 : 0; belugas@7774: _industry_sort_dirty = true; belugas@7774: SetWindowDirty(w); belugas@7774: } break; truelight@0: belugas@7774: case DIRECTORY_INDU_SORTBYTYPE: { belugas@7774: _industry_sort_order = _industry_sort_order == 2 ? 3 : 2; belugas@7774: _industry_sort_dirty = true; belugas@7774: SetWindowDirty(w); belugas@7774: } break; truelight@0: belugas@7774: case DIRECTORY_INDU_SORTBYPROD: { belugas@7774: _industry_sort_order = _industry_sort_order == 4 ? 5 : 4; belugas@7774: _industry_sort_dirty = true; belugas@7774: SetWindowDirty(w); belugas@7774: } break; truelight@193: belugas@7774: case DIRECTORY_INDU_SORTBYTRANSPORT: { belugas@7774: _industry_sort_order = _industry_sort_order == 6 ? 7 : 6; belugas@7774: _industry_sort_dirty = true; belugas@7774: SetWindowDirty(w); belugas@7774: } break; truelight@0: belugas@7774: case DIRECTORY_INDU_SHOWINDU: { belugas@7774: int y = (e->we.click.pt.y - 28) / 10; belugas@7774: uint16 p; belugas@7774: belugas@7774: if (!IS_INT_INSIDE(y, 0, w->vscroll.cap)) return; belugas@7774: p = y + w->vscroll.pos; belugas@7774: if (p < _num_industry_sort) { belugas@7774: ScrollMainWindowToTile(_industry_sort[p]->xy); belugas@7774: } belugas@7774: } break; truelight@0: } truelight@0: break; truelight@0: truelight@0: case WE_4: truelight@0: SetWindowDirty(w); truelight@0: break; truelight@867: truelight@867: case WE_RESIZE: belugas@4634: w->vscroll.cap += e->we.sizing.diff.y / 10; truelight@867: break; truelight@193: } truelight@0: } truelight@0: truelight@0: truelight@0: /* Industry List */ truelight@0: static const WindowDesc _industry_directory_desc = { rubidium@7837: WDP_AUTO, WDP_AUTO, 508, 190, 508, 190, rubidium@6144: WC_INDUSTRY_DIRECTORY, WC_NONE, truelight@867: WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE, truelight@0: _industry_directory_widgets, truelight@0: IndustryDirectoryWndProc truelight@0: }; truelight@0: truelight@0: rubidium@6573: void ShowIndustryDirectory() truelight@0: { belugas@4171: Window *w = AllocateWindowDescFront(&_industry_directory_desc, 0); truelight@0: tron@2989: if (w != NULL) { truelight@0: w->vscroll.cap = 16; truelight@867: w->resize.height = w->height - 6 * 10; // minimum 10 items truelight@867: w->resize.step_height = 10; truelight@0: SetWindowDirty(w); truelight@0: } truelight@0: }