/* * 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 programmable_signals_gui.cpp GUI related to programming signals */ #include "stdafx.h" #include "programmable_signals.h" #include "command_func.h" #include "window_func.h" #include "strings_func.h" #include "string_func.h" #include "viewport_func.h" #include "textbuf_gui.h" #include "company_func.h" #include "widgets/dropdown_func.h" #include "gui.h" #include "gfx_func.h" #include "tilehighlight_func.h" #include "rail_map.h" #include "tile_cmd.h" #include "error.h" #include "scope.h" #include "zoom_func.h" #include "table/sprites.h" #include "table/strings.h" DropDownList GetSlotDropDownList(Owner owner, TraceRestrictSlotID slot_id, int &selected, VehicleType vehtype, bool show_other_types); DropDownList GetCounterDropDownList(Owner owner, TraceRestrictCounterID ctr_id, int &selected); enum ProgramWindowWidgets { PROGRAM_WIDGET_CAPTION, PROGRAM_WIDGET_INSTRUCTION_LIST, PROGRAM_WIDGET_SCROLLBAR, PROGRAM_WIDGET_SEL_TOP_LEFT, PROGRAM_WIDGET_SEL_TOP_AUX, PROGRAM_WIDGET_SEL_TOP_MIDDLE, PROGRAM_WIDGET_SEL_TOP_RIGHT, PROGRAM_WIDGET_SET_STATE, PROGRAM_WIDGET_COND_VARIABLE, PROGRAM_WIDGET_COND_COMPARATOR, PROGRAM_WIDGET_COND_VALUE, PROGRAM_WIDGET_COND_GOTO_SIGNAL, PROGRAM_WIDGET_COND_SET_SIGNAL, PROGRAM_WIDGET_COND_SLOT, PROGRAM_WIDGET_COND_COUNTER, PROGRAM_WIDGET_GOTO_SIGNAL, PROGRAM_WIDGET_INSERT, PROGRAM_WIDGET_REMOVE, PROGRAM_WIDGET_REMOVE_PROGRAM, PROGRAM_WIDGET_COPY_PROGRAM, }; enum PanelWidgets { // Left DPL_COND_VARIABLE = 0, DPL_SET_STATE, // Aux, DPA_SLOT = 0, DPA_COUNTER = 1, // Middle DPM_COND_COMPARATOR = 0, DPM_COND_GOTO_SIGNAL, // Right DPR_COND_VALUE = 0, DPR_COND_SET_SIGNAL }; static const StringID _program_insert[] = { STR_PROGSIG_INSERT_IF, STR_PROGSIG_INSERT_SET_SIGNAL, INVALID_STRING_ID }; static SignalOpcode OpcodeForIndex(int index) { switch (index) { case 0: return PSO_IF; case 1: return PSO_SET_SIGNAL; default: NOT_REACHED(); } } static bool IsConditionComparator(SignalCondition *cond) { switch (cond->ConditionCode()) { case PSC_NUM_GREEN: case PSC_NUM_RED: case PSC_SLOT_OCC: case PSC_SLOT_OCC_REM: case PSC_COUNTER: return true; default: return false; } } static const StringID _program_condvar[] = { /* PSC_ALWAYS */ STR_PROGSIG_COND_ALWAYS, /* PSC_NEVER */ STR_PROGSIG_COND_NEVER, /* PSC_NUM_GREEN */ STR_PROGSIG_CONDVAR_NUM_GREEN, /* PSC_NUM_RED */ STR_PROGSIG_CONDVAR_NUM_RED, /* PSC_SIGNAL_STATE*/ STR_PROGSIG_COND_SIGNAL_STATE, /* PSC_SLOT_OCC*/ STR_PROGSIG_COND_SLOT, /* PSC_SLOT_OCC_REM*/ STR_PROGSIG_COND_SLOT_REMAINING, /* PSC_COUNTER*/ STR_PROGSIG_COND_COUNTER, INVALID_STRING_ID }; // TODO: These should probably lose the ORDER static const StringID _program_comparator[] = { /* SGC_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_EQUALS, /* SGC_NOT_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_NOT_EQUALS, /* SGC_LESS_THAN */ STR_ORDER_CONDITIONAL_COMPARATOR_LESS_THAN, /* SGC_LESS_THAN_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_LESS_EQUALS, /* SGC_MORE_THAN */ STR_ORDER_CONDITIONAL_COMPARATOR_MORE_THAN, /* SGC_MORE_THAN_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_MORE_EQUALS, /* SGC_IS_TRUE */ STR_ORDER_CONDITIONAL_COMPARATOR_IS_TRUE, /* SGC_IS_FALSE */ STR_ORDER_CONDITIONAL_COMPARATOR_IS_FALSE, INVALID_STRING_ID }; static const uint _program_comparator_hide_mask = 0xC0; static const StringID _program_sigstate[] = { STR_COLOUR_RED, STR_COLOUR_GREEN, INVALID_STRING_ID }; /** Get the string for a condition */ static std::string GetConditionString(SignalCondition *cond) { StringID string = INVALID_STRING_ID; if (cond->ConditionCode() == PSC_SLOT_OCC || cond->ConditionCode() == PSC_SLOT_OCC_REM) { SignalSlotCondition *scc = static_cast(cond); if (scc->IsSlotValid()) { string = (cond->ConditionCode() == PSC_SLOT_OCC_REM) ? STR_PROGSIG_COND_SLOT_REMAINING_COMPARE : STR_PROGSIG_COND_SLOT_COMPARE; SetDParam(0, scc->slot_id); } else { string = (cond->ConditionCode() == PSC_SLOT_OCC_REM) ? STR_PROGSIG_COND_SLOT_REMAINING_COMPARE_INVALID : STR_PROGSIG_COND_SLOT_COMPARE_INVALID; SetDParam(0, STR_TRACE_RESTRICT_VARIABLE_UNDEFINED_RED); } SetDParam(1, _program_comparator[scc->comparator]); SetDParam(2, scc->value); } else if (cond->ConditionCode() == PSC_COUNTER) { SignalCounterCondition *scc = static_cast(cond); if (scc->IsCounterValid()) { string = STR_PROGSIG_COND_COUNTER_COMPARE; SetDParam(0, scc->ctr_id); } else { string = STR_PROGSIG_COND_COUNTER_COMPARE_INVALID; SetDParam(0, STR_TRACE_RESTRICT_VARIABLE_UNDEFINED_RED); } SetDParam(1, _program_comparator[scc->comparator]); SetDParam(2, scc->value); } else if (IsConditionComparator(cond)) { SignalConditionComparable *cv = static_cast(cond); string = STR_PROGSIG_COND_COMPARE; SetDParam(0, _program_condvar[cond->ConditionCode()]); SetDParam(1, _program_comparator[cv->comparator]); SetDParam(2, cv->value); } else { string = _program_condvar[cond->ConditionCode()]; if (cond->ConditionCode() == PSC_SIGNAL_STATE) { SignalStateCondition *sig_cond = static_cast(cond); if (sig_cond->IsSignalValid()) { string = STR_PROGSIG_CONDVAR_SIGNAL_STATE_SPECIFIED; SetDParam(0, TileX(sig_cond->sig_tile)); SetDParam(1, TileY(sig_cond->sig_tile)); } else { string = STR_PROGSIG_CONDVAR_SIGNAL_STATE_UNSPECIFIED; } } } return GetString(string); } /** * Draws an instruction in the programming GUI * @param instruction The instruction to draw * @param y Y position for drawing * @param selected True, if the order is selected * @param indent How many levels the instruction is indented * @param left Left border for text drawing * @param right Right border for text drawing */ static void DrawInstructionString(SignalInstruction *instruction, int y, bool selected, int indent, int left, int right) { StringID instruction_string = INVALID_STRING_ID; switch (instruction->Opcode()) { case PSO_FIRST: instruction_string = STR_PROGSIG_FIRST; break; case PSO_LAST: instruction_string = STR_PROGSIG_LAST; break; case PSO_IF: { SignalIf *if_ins = static_cast(instruction); SetDParamStr(0, GetConditionString(if_ins->condition)); instruction_string = STR_PROGSIG_IF; break; } case PSO_IF_ELSE: instruction_string = STR_PROGSIG_ELSE; break; case PSO_IF_ENDIF: instruction_string = STR_PROGSIG_ENDIF; break; case PSO_SET_SIGNAL: { instruction_string = STR_PROGSIG_SET_SIGNAL; SignalSet *set = static_cast(instruction); SetDParam(0, _program_sigstate[set->to_state]); break; } default: NOT_REACHED(); } bool rtl = _current_text_dir == TD_RTL; DrawString(left + (rtl ? 0 : ScaleGUITrad(indent * 16)), right - (rtl ? ScaleGUITrad(indent * 16) : 0), y, instruction_string, selected ? TC_WHITE : TC_BLACK); } struct GuiInstruction { SignalInstruction *insn; uint indent; }; typedef std::vector GuiInstructionList; class ProgramWindow: public Window { public: ProgramWindow(WindowDesc *desc, SignalReference ref): Window(desc) { // this->InitNested(desc, (ref.tile << 3) | ref.track); this->tile = ref.tile; this->track = ref.track; this->selected_instruction = -1; this->CreateNestedTree(); this->vscroll = this->GetScrollbar(PROGRAM_WIDGET_SCROLLBAR); this->GetWidget(PROGRAM_WIDGET_SEL_TOP_AUX)->SetDisplayedPlane(SZSP_NONE); this->current_aux_plane = SZSP_NONE; this->FinishInitNested((ref.tile << 3) | ref.track); program = GetSignalProgram(ref); RebuildInstructionList(); } virtual void OnClick(Point pt, WidgetID widget, int click_count) override { switch (widget) { case PROGRAM_WIDGET_INSTRUCTION_LIST: { int sel = this->GetInstructionFromPt(pt.y); this->CloseChildWindows(); HideDropDownMenu(this); if (sel == -1 || this->GetOwner() != _local_company) { // Deselect this->selected_instruction = -1; } else { this->selected_instruction = sel; } this->UpdateButtonState(); } break; case PROGRAM_WIDGET_INSERT: { DEBUG(misc, 5, "Selection is %d", this->selected_instruction); if (this->GetOwner() != _local_company || this->selected_instruction < 1) return; ShowDropDownMenu(this, _program_insert, -1, PROGRAM_WIDGET_INSERT, 0, 0, 0); } break; case PROGRAM_WIDGET_REMOVE: { SignalInstruction *ins = GetSelected(); if (ins == nullptr) return; uint32_t p1 = 0; SB(p1, 0, 3, this->track); SB(p1, 3, 16, ins->Id()); DoCommandP(this->tile, p1, 0, CMD_REMOVE_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_REMOVE_INSTRUCTION)); this->RebuildInstructionList(); } break; case PROGRAM_WIDGET_SET_STATE: { SignalInstruction *si = this->GetSelected(); if (!si || si->Opcode() != PSO_SET_SIGNAL) return; SignalSet *ss = static_cast (si); ShowDropDownMenu(this, _program_sigstate, ss->to_state, PROGRAM_WIDGET_SET_STATE, 0, 0, 0); } break; case PROGRAM_WIDGET_COND_VARIABLE: { SignalInstruction *si = this->GetSelected(); if (!si || si->Opcode() != PSO_IF) return; SignalIf *sif = static_cast (si); ShowDropDownMenu(this, _program_condvar, sif->condition->ConditionCode(), PROGRAM_WIDGET_COND_VARIABLE, 0, _settings_client.gui.show_adv_tracerestrict_features ? 0 : 0xE0, 0); this->UpdateButtonState(); } break; case PROGRAM_WIDGET_COND_COMPARATOR: { SignalInstruction *si = this->GetSelected(); if (!si || si->Opcode() != PSO_IF) return; SignalIf *sif = static_cast (si); if (!IsConditionComparator(sif->condition)) return; SignalConditionComparable *vc = static_cast(sif->condition); ShowDropDownMenu(this, _program_comparator, vc->comparator, PROGRAM_WIDGET_COND_COMPARATOR, 0, _program_comparator_hide_mask, 0); } break; case PROGRAM_WIDGET_COND_VALUE: { SignalInstruction *si = this->GetSelected(); if (!si || si->Opcode() != PSO_IF) return; SignalIf *sif = static_cast (si); if (!IsConditionComparator(sif->condition)) return; SignalConditionComparable *vc = static_cast(sif->condition); SetDParam(0, vc->value); //ShowQueryString(STR_JUST_INT, STR_PROGSIG_CONDITION_VALUE_CAPT, 5, 100, this, CS_NUMERAL, QSF_NONE); ShowQueryString(STR_JUST_INT, STR_PROGSIG_CONDITION_VALUE_CAPT, 5, this, CS_NUMERAL, QSF_NONE); this->UpdateButtonState(); } break; case PROGRAM_WIDGET_COND_GOTO_SIGNAL: { SignalInstruction *si = this->GetSelected(); if (!si || si->Opcode() != PSO_IF) return; SignalIf *sif = static_cast (si); if (sif->condition->ConditionCode() != PSC_SIGNAL_STATE) return; SignalStateCondition *sc = static_cast(sif->condition); if (sc->IsSignalValid()) { ScrollMainWindowToTile(sc->sig_tile); } else { ShowErrorMessage(STR_ERROR_CAN_T_GOTO_UNDEFINED_SIGNAL, STR_EMPTY, WL_INFO); } // this->RaiseWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL); } break; case PROGRAM_WIDGET_COND_SLOT: { SignalInstruction *si = this->GetSelected(); if (!si || si->Opcode() != PSO_IF) return; SignalIf *sif = static_cast (si); if (sif->condition->ConditionCode() != PSC_SLOT_OCC && sif->condition->ConditionCode() != PSC_SLOT_OCC_REM) return; SignalSlotCondition *sc = static_cast(sif->condition); int selected; DropDownList list = GetSlotDropDownList(this->GetOwner(), sc->slot_id, selected, VEH_TRAIN, true); if (!list.empty()) ShowDropDownList(this, std::move(list), selected, PROGRAM_WIDGET_COND_SLOT); } break; case PROGRAM_WIDGET_COND_COUNTER: { SignalInstruction *si = this->GetSelected(); if (!si || si->Opcode() != PSO_IF) return; SignalIf *sif = static_cast (si); if (sif->condition->ConditionCode() != PSC_COUNTER) return; SignalCounterCondition *sc = static_cast(sif->condition); int selected; DropDownList list = GetCounterDropDownList(this->GetOwner(), sc->ctr_id, selected); if (!list.empty()) ShowDropDownList(this, std::move(list), selected, PROGRAM_WIDGET_COND_COUNTER); } break; case PROGRAM_WIDGET_COND_SET_SIGNAL: { this->ToggleWidgetLoweredState(PROGRAM_WIDGET_COND_SET_SIGNAL); this->SetWidgetDirty(PROGRAM_WIDGET_COND_SET_SIGNAL); if (this->IsWidgetLowered(PROGRAM_WIDGET_COND_SET_SIGNAL)) { SetObjectToPlaceWnd(ANIMCURSOR_BUILDSIGNALS, PAL_NONE, HT_RECT, this); } else { ResetObjectToPlace(); } } break; case PROGRAM_WIDGET_GOTO_SIGNAL: { ScrollMainWindowToTile(this->tile); // this->RaiseWidget(PROGRAM_WIDGET_GOTO_SIGNAL); } break; case PROGRAM_WIDGET_REMOVE_PROGRAM: { DoCommandP(this->tile, this->track | (SPMC_REMOVE << 3), 0, CMD_SIGNAL_PROGRAM_MGMT | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION)); this->RebuildInstructionList(); } break; case PROGRAM_WIDGET_COPY_PROGRAM: { this->ToggleWidgetLoweredState(PROGRAM_WIDGET_COPY_PROGRAM); this->SetWidgetDirty(PROGRAM_WIDGET_COPY_PROGRAM); if (this->IsWidgetLowered(PROGRAM_WIDGET_COPY_PROGRAM)) { SetObjectToPlaceWnd(ANIMCURSOR_BUILDSIGNALS, PAL_NONE, HT_RECT, this); } else { ResetObjectToPlace(); } } break; } } virtual void OnPlaceObject(Point pt, TileIndex tile1) override { if (this->IsWidgetLowered(PROGRAM_WIDGET_COPY_PROGRAM)) { //Copy program from another progsignal TrackBits trackbits = TrackdirBitsToTrackBits(GetTileTrackdirBits(tile1, TRANSPORT_RAIL, 0)); if (trackbits & TRACK_BIT_VERT) { // N-S direction trackbits = (_tile_fract_coords.x <= _tile_fract_coords.y) ? TRACK_BIT_RIGHT : TRACK_BIT_LEFT; } if (trackbits & TRACK_BIT_HORZ) { // E-W direction trackbits = (_tile_fract_coords.x + _tile_fract_coords.y <= 15) ? TRACK_BIT_UPPER : TRACK_BIT_LOWER; } Track track1 = FindFirstTrack(trackbits); if(track1 == INVALID_TRACK) { return; } Trackdir td = TrackToTrackdir(track1); Trackdir tdr = ReverseTrackdir(td); if (!(HasSignalOnTrackdir(tile1, td) || HasSignalOnTrackdir(tile1, tdr))) return; if (GetSignalType(tile1, track1) != SIGTYPE_PROG) { ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_NOT_AN_PROG_SIGNAL, WL_INFO); return; } if(this->tile == tile1 && this->track == track1) { ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_CANNOT_USE_SELF, WL_INFO); return; } SignalProgram *sp = GetExistingSignalProgram(SignalReference(tile1, track1)); if (!sp) { ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_NOT_AN_EXIT_SIGNAL, WL_INFO); return; } DoCommandP(this->tile, this->track | (SPMC_CLONE << 3) | (track1 << 7), tile1, CMD_SIGNAL_PROGRAM_MGMT | CMD_MSG(STR_ERROR_CAN_T_INSERT_INSTRUCTION)); ResetObjectToPlace(); this->RaiseWidget(PROGRAM_WIDGET_COPY_PROGRAM); this->RebuildInstructionList(); //OnPaint(); // this appears to cause visual artefacts return; } SignalInstruction *si = this->GetSelected(); if (!si || si->Opcode() != PSO_IF) return; SignalIf *sif = static_cast (si); if (sif->condition->ConditionCode() != PSC_SIGNAL_STATE) return; if (!IsPlainRailTile(tile1)) { return; } TrackBits trackbits = TrackdirBitsToTrackBits(GetTileTrackdirBits(tile1, TRANSPORT_RAIL, 0)); if (trackbits & TRACK_BIT_VERT) { // N-S direction trackbits = (_tile_fract_coords.x <= _tile_fract_coords.y) ? TRACK_BIT_RIGHT : TRACK_BIT_LEFT; } if (trackbits & TRACK_BIT_HORZ) { // E-W direction trackbits = (_tile_fract_coords.x + _tile_fract_coords.y <= 15) ? TRACK_BIT_UPPER : TRACK_BIT_LOWER; } Track track1 = FindFirstTrack(trackbits); if(track1 == INVALID_TRACK) { return; } Trackdir td = TrackToTrackdir(track1); Trackdir tdr = ReverseTrackdir(td); if (HasSignalOnTrackdir(tile1, td) && HasSignalOnTrackdir(tile1, tdr)) { ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_CAN_T_DEPEND_UPON_BIDIRECTIONAL_SIGNALS, WL_INFO); return; } else if (HasSignalOnTrackdir(tile1, tdr) && !HasSignalOnTrackdir(tile1, td)) { td = tdr; } if (!HasSignalOnTrackdir(tile1, td)) { return; } //!!!!!!!!!!!!!!! if (!(GetSignalType(tile1, track1) == SIGTYPE_EXIT || GetSignalType(tile1, track1) == SIGTYPE_PROG)) { //!!!!!!!!!!!!!!! ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_NOT_AN_EXIT_SIGNAL, WL_INFO); return; } uint32_t p1 = 0, p2 = 0; SB(p1, 0, 3, this->track); SB(p1, 3, 16, si->Id()); SB(p2, 0, 1, 1); SB(p2, 1, 4, td); SB(p2, 5, 27, tile1); DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION)); ResetObjectToPlace(); this->RaiseWidget(PROGRAM_WIDGET_COND_SET_SIGNAL); //OnPaint(); // this appears to cause visual artefacts } virtual void OnQueryTextFinished(char *str) override { if (!StrEmpty(str)) { SignalInstruction *si = this->GetSelected(); if (!si || si->Opcode() != PSO_IF) return; SignalIf *sif = static_cast (si); if (!IsConditionComparator(sif->condition)) return; uint value = atoi(str); uint32_t p1 = 0, p2 = 0; SB(p1, 0, 3, this->track); SB(p1, 3, 16, si->Id()); SB(p2, 0, 1, 1); SB(p2, 1, 2, SCF_VALUE); SB(p2, 3, 27, value); DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION)); } } virtual void OnDropdownSelect(WidgetID widget, int index) override { SignalInstruction *ins = this->GetSelected(); if (!ins) return; switch (widget) { case PROGRAM_WIDGET_INSERT: { uint64_t p1 = 0; SB(p1, 0, 3, this->track); SB(p1, 3, 16, ins->Id()); SB(p1, 19, 8, OpcodeForIndex(index)); DoCommandP(this->tile, p1, 0, CMD_INSERT_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_INSERT_INSTRUCTION)); this->RebuildInstructionList(); break; } case PROGRAM_WIDGET_SET_STATE: { uint64_t p1 = 0; SB(p1, 0, 3, this->track); SB(p1, 3, 16, ins->Id()); DoCommandP(this->tile, p1, index, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION)); break; } case PROGRAM_WIDGET_COND_VARIABLE: { uint64_t p1 = 0, p2 = 0; SB(p1, 0, 3, this->track); SB(p1, 3, 16, ins->Id()); SB(p2, 0, 1, 0); SB(p2, 1, 8, index); DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION)); break; } case PROGRAM_WIDGET_COND_COMPARATOR: { uint64_t p1 = 0, p2 = 0; SB(p1, 0, 3, this->track); SB(p1, 3, 16, ins->Id()); SB(p2, 0, 1, 1); SB(p2, 1, 2, SCF_COMPARATOR); SB(p2, 3, 27, index); DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION)); break; } case PROGRAM_WIDGET_COND_SLOT: case PROGRAM_WIDGET_COND_COUNTER: { uint64_t p1 = 0, p2 = 0; SB(p1, 0, 3, this->track); SB(p1, 3, 16, ins->Id()); SB(p2, 0, 1, 1); SB(p2, 1, 2, SCF_SLOT_COUNTER); SB(p2, 3, 27, index); DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION)); } } } virtual void UpdateWidgetSize(WidgetID widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override { switch (widget) { case PROGRAM_WIDGET_INSTRUCTION_LIST: resize->height = GetCharacterHeight(FS_NORMAL); size->height = 6 * resize->height + WidgetDimensions::scaled.framerect.Vertical(); break; } } virtual void OnResize() override { /* Update the scroll bar */ this->vscroll->SetCapacityFromWidget(this, PROGRAM_WIDGET_INSTRUCTION_LIST); } virtual void OnPaint() override { this->DrawWidgets(); } virtual void DrawWidget(const Rect &r, WidgetID widget) const override { if (widget != PROGRAM_WIDGET_INSTRUCTION_LIST) return; Rect ir = r.Shrink(WidgetDimensions::scaled.framerect); int y = ir.top; int line_height = this->GetWidget(PROGRAM_WIDGET_INSTRUCTION_LIST)->resize_y; for (int no = this->vscroll->GetPosition(); no < (int) instructions.size(); no++) { const GuiInstruction &i = instructions[no]; /* Don't draw anything if it extends past the end of the window. */ if (!this->vscroll->IsVisible(no)) break; DrawInstructionString(i.insn, y, no == this->selected_instruction, i.indent, ir.left, ir.right); y += line_height; } } virtual void OnInvalidateData(int data, bool gui_scope) override { if (gui_scope) { this->RebuildInstructionList(); } } virtual void SetStringParameters(WidgetID widget) const override { switch (widget) { case PROGRAM_WIDGET_COND_VALUE: { SetDParam(0, 0); SignalInstruction *insn = this->GetSelected(); if (!insn || insn->Opcode() != PSO_IF) return; SignalIf *si = static_cast(insn); if (!IsConditionComparator(si->condition)) return; SignalConditionComparable *vc = static_cast(si->condition); SetDParam(0, vc->value); } break; case PROGRAM_WIDGET_COND_SLOT: { SetDParam(0, 0); SignalInstruction *insn = this->GetSelected(); if (!insn || insn->Opcode() != PSO_IF) return; SignalIf *si = static_cast(insn); if (si->condition->ConditionCode() != PSC_SLOT_OCC && si->condition->ConditionCode() != PSC_SLOT_OCC_REM) return; SignalSlotCondition *sc = static_cast(si->condition); SetDParam(0, sc->slot_id); } break; case PROGRAM_WIDGET_COND_COUNTER: { SetDParam(0, 0); SignalInstruction *insn = this->GetSelected(); if (!insn || insn->Opcode() != PSO_IF) return; SignalIf *si = static_cast(insn); if (si->condition->ConditionCode() != PSC_COUNTER) return; SignalCounterCondition *sc = static_cast(si->condition); SetDParam(0, sc->ctr_id); } break; } } private: SignalInstruction *GetSelected() const { if (this->selected_instruction == -1 || this->selected_instruction >= int(this->instructions.size())) return nullptr; return this->instructions[this->selected_instruction].insn; } Owner GetOwner() { return GetTileOwner(this->tile); } int GetInstructionFromPt(int y) { NWidgetBase *nwid = this->GetWidget(PROGRAM_WIDGET_INSTRUCTION_LIST); int32_t sel = (y - nwid->pos_y - WidgetDimensions::scaled.framerect.top) / nwid->resize_y; // Selected line if (sel >= this->vscroll->GetCapacity()) return -1; sel += this->vscroll->GetPosition(); return (sel <= (int32_t)(this->instructions.size()) && sel >= 0) ? sel : -1; } void RebuildInstructionList() { uint old_len = (uint)this->instructions.size(); this->instructions.clear(); SignalInstruction *insn = program->first_instruction; uint indent = 0; do { DEBUG(misc, 5, "PSig Gui: Opcode %d", insn->Opcode()); switch (insn->Opcode()) { case PSO_FIRST: case PSO_LAST: { SignalSpecial *s = static_cast(insn); this->instructions.emplace_back(); GuiInstruction *gi = &(this->instructions.back()); gi->insn = s; gi->indent = indent; insn = s->next; break; } case PSO_IF: { SignalIf *i = static_cast(insn); this->instructions.emplace_back(); GuiInstruction *gi = &(this->instructions.back()); gi->insn = i; gi->indent = indent++; insn = i->if_true; break; } case PSO_IF_ELSE: { SignalIf::PseudoInstruction *p = static_cast(insn); this->instructions.emplace_back(); GuiInstruction *gi = &(this->instructions.back()); gi->insn = p; gi->indent = indent - 1; insn = p->block->if_false; break; } case PSO_IF_ENDIF: { SignalIf::PseudoInstruction *p = static_cast(insn); this->instructions.emplace_back(); GuiInstruction *gi = &(this->instructions.back()); gi->insn = p; gi->indent = --indent; insn = p->block->after; break; } case PSO_SET_SIGNAL: { SignalSet *s = static_cast(insn); this->instructions.emplace_back(); GuiInstruction *gi = &(this->instructions.back()); gi->insn = s; gi->indent = indent; insn = s->next; break; } default: NOT_REACHED(); } } while (insn); this->vscroll->SetCount((uint)this->instructions.size()); if (this->instructions.size() != old_len) selected_instruction = -1; UpdateButtonState(); } void UpdateButtonState() { // Do not close the Signals GUI when opening the ProgrammableSignals GUI // ResetObjectToPlace(); this->RaiseWidget(PROGRAM_WIDGET_INSERT); this->RaiseWidget(PROGRAM_WIDGET_REMOVE); this->RaiseWidget(PROGRAM_WIDGET_SET_STATE); this->RaiseWidget(PROGRAM_WIDGET_COND_VARIABLE); this->RaiseWidget(PROGRAM_WIDGET_COND_COMPARATOR); this->RaiseWidget(PROGRAM_WIDGET_COND_VALUE); this->RaiseWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL); NWidgetStacked *left_sel = this->GetWidget(PROGRAM_WIDGET_SEL_TOP_LEFT); NWidgetStacked *aux_sel = this->GetWidget(PROGRAM_WIDGET_SEL_TOP_AUX); NWidgetStacked *middle_sel = this->GetWidget(PROGRAM_WIDGET_SEL_TOP_MIDDLE); NWidgetStacked *right_sel = this->GetWidget(PROGRAM_WIDGET_SEL_TOP_RIGHT); auto aux_sel_guard = scope_guard([&]() { if (this->current_aux_plane != aux_sel->shown_plane) { this->current_aux_plane = aux_sel->shown_plane; this->ReInit(); } }); // Disable all the modifier buttons - we will re-enable them if applicable this->DisableWidget(PROGRAM_WIDGET_SET_STATE); this->DisableWidget(PROGRAM_WIDGET_COND_VARIABLE); this->DisableWidget(PROGRAM_WIDGET_COND_COMPARATOR); this->DisableWidget(PROGRAM_WIDGET_COND_VALUE); this->DisableWidget(PROGRAM_WIDGET_COND_SET_SIGNAL); this->DisableWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL); // Don't allow modifications if don't own, or have selected invalid instruction if (this->GetOwner() != _local_company || this->selected_instruction < 1) { this->DisableWidget(PROGRAM_WIDGET_INSERT); this->DisableWidget(PROGRAM_WIDGET_REMOVE); this->SetDirty(); return; } else { this->EnableWidget(PROGRAM_WIDGET_INSERT); this->EnableWidget(PROGRAM_WIDGET_REMOVE); } SignalInstruction *insn = GetSelected(); if (!insn) return; aux_sel->SetDisplayedPlane(SZSP_NONE); switch (insn->Opcode()) { case PSO_IF: { SignalIf *i = static_cast(insn); left_sel->SetDisplayedPlane(DPL_COND_VARIABLE); middle_sel->SetDisplayedPlane(DPM_COND_COMPARATOR); right_sel->SetDisplayedPlane(DPR_COND_VALUE); this->EnableWidget(PROGRAM_WIDGET_COND_VARIABLE); this->GetWidget(PROGRAM_WIDGET_COND_VARIABLE)->widget_data = _program_condvar[i->condition->ConditionCode()]; if (IsConditionComparator(i->condition)) { SignalConditionComparable *vc = static_cast(i->condition); this->EnableWidget(PROGRAM_WIDGET_COND_COMPARATOR); this->EnableWidget(PROGRAM_WIDGET_COND_VALUE); this->GetWidget(PROGRAM_WIDGET_COND_COMPARATOR)->widget_data = _program_comparator[vc->comparator]; } else if (i->condition->ConditionCode() == PSC_SIGNAL_STATE) { this->EnableWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL); this->EnableWidget(PROGRAM_WIDGET_COND_SET_SIGNAL); middle_sel->SetDisplayedPlane(DPM_COND_GOTO_SIGNAL); right_sel->SetDisplayedPlane(DPR_COND_SET_SIGNAL); } if (i->condition->ConditionCode() == PSC_SLOT_OCC || i->condition->ConditionCode() == PSC_SLOT_OCC_REM) { SignalSlotCondition *scc = static_cast(i->condition); this->GetWidget(PROGRAM_WIDGET_COND_SLOT)->widget_data = scc->IsSlotValid() ? STR_TRACE_RESTRICT_SLOT_NAME : STR_TRACE_RESTRICT_VARIABLE_UNDEFINED; aux_sel->SetDisplayedPlane(DPA_SLOT); } if (i->condition->ConditionCode() == PSC_COUNTER) { SignalCounterCondition *scc = static_cast(i->condition); this->GetWidget(PROGRAM_WIDGET_COND_COUNTER)->widget_data = scc->IsCounterValid() ? STR_TRACE_RESTRICT_COUNTER_NAME : STR_TRACE_RESTRICT_VARIABLE_UNDEFINED; aux_sel->SetDisplayedPlane(DPA_COUNTER); } } break; case PSO_SET_SIGNAL: { SignalSet *s = static_cast(insn); left_sel->SetDisplayedPlane(DPL_SET_STATE); this->SetWidgetDisabledState(PROGRAM_WIDGET_SET_STATE, false); this->GetWidget(PROGRAM_WIDGET_SET_STATE)->widget_data = _program_sigstate[s->to_state]; } break; case PSO_FIRST: case PSO_LAST: case PSO_IF_ELSE: case PSO_IF_ENDIF: // All cannot be modified this->DisableWidget(PROGRAM_WIDGET_REMOVE); break; default: NOT_REACHED(); } this->SetDirty(); } TileIndex tile; Track track; SignalProgram *program; GuiInstructionList instructions; int selected_instruction; Scrollbar *vscroll; int current_aux_plane; }; static constexpr NWidgetPart _nested_program_widgets[] = { // Title bar NWidget(NWID_HORIZONTAL), NWidget(WWT_CLOSEBOX, COLOUR_GREY), NWidget(WWT_CAPTION, COLOUR_GREY, PROGRAM_WIDGET_CAPTION), SetDataTip(STR_PROGSIG_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), NWidget(WWT_SHADEBOX, COLOUR_GREY), NWidget(WWT_DEFSIZEBOX, COLOUR_GREY), NWidget(WWT_STICKYBOX, COLOUR_GREY), EndContainer(), // Program display NWidget(NWID_HORIZONTAL), NWidget(WWT_PANEL, COLOUR_GREY, PROGRAM_WIDGET_INSTRUCTION_LIST), SetMinimalSize(372, 62), SetDataTip(0x0, STR_NULL), SetResize(1, 1), EndContainer(), NWidget(NWID_VSCROLLBAR, COLOUR_GREY, PROGRAM_WIDGET_SCROLLBAR), EndContainer(), // Button Bar NWidget(NWID_HORIZONTAL), NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), NWidget(NWID_SELECTION, INVALID_COLOUR, PROGRAM_WIDGET_SEL_TOP_LEFT), NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_COND_VARIABLE), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_NULL, STR_PROGSIG_COND_VARIABLE_TOOLTIP), SetResize(1, 0), NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_SET_STATE), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_NULL, STR_PROGSIG_SIGNAL_STATE_TOOLTIP), SetResize(1, 0), EndContainer(), NWidget(NWID_SELECTION, INVALID_COLOUR, PROGRAM_WIDGET_SEL_TOP_AUX), NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_COND_SLOT), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_NULL, STR_PROGSIG_COND_SLOT_TOOLTIP), SetResize(1, 0), NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_COND_COUNTER), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_NULL, STR_PROGSIG_COND_COUNTER_TOOLTIP), SetResize(1, 0), EndContainer(), NWidget(NWID_SELECTION, INVALID_COLOUR, PROGRAM_WIDGET_SEL_TOP_MIDDLE), NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_COND_COMPARATOR), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_NULL, STR_PROGSIG_COND_COMPARATOR_TOOLTIP), SetResize(1, 0), NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COND_GOTO_SIGNAL), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_PROGSIG_GOTO_SIGNAL, STR_PROGSIG_GOTO_SIGNAL_TOOLTIP), SetResize(1, 0), EndContainer(), NWidget(NWID_SELECTION, INVALID_COLOUR, PROGRAM_WIDGET_SEL_TOP_RIGHT), NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COND_VALUE), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_JUST_COMMA, STR_PROGSIG_COND_VALUE_TOOLTIP), SetResize(1, 0), NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COND_SET_SIGNAL), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_PROGSIG_COND_SET_SIGNAL, STR_PROGSIG_COND_SET_SIGNAL_TOOLTIP), SetResize(1, 0), EndContainer(), EndContainer(), NWidget(WWT_IMGBTN, COLOUR_GREY, PROGRAM_WIDGET_GOTO_SIGNAL), SetMinimalSize(12, 12), SetDataTip(SPR_ARROW_RIGHT, STR_PROGSIG_GOTO_SIGNAL_TOOLTIP), EndContainer(), /* Second button row. */ NWidget(NWID_HORIZONTAL), NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_INSERT), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_PROGSIG_INSERT, STR_PROGSIG_INSERT_TOOLTIP), SetResize(1, 0), NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_REMOVE), SetMinimalSize(186, 12), SetFill(1, 0), SetDataTip(STR_PROGSIG_REMOVE, STR_PROGSIG_REMOVE_TOOLTIP), SetResize(1, 0), EndContainer(), EndContainer(), /* Third button row*/ NWidget(NWID_HORIZONTAL), NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_REMOVE_PROGRAM), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_PROGSIG_REMOVE_PROGRAM, STR_PROGSIG_REMOVE_PROGRAM_TOOLTIP), SetResize(1, 0), NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COPY_PROGRAM), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_PROGSIG_COPY_PROGRAM, STR_PROGSIG_COPY_PROGRAM_TOOLTIP), SetResize(1, 0), NWidget(WWT_RESIZEBOX, COLOUR_GREY), EndContainer(), }; static WindowDesc _program_desc(__FILE__, __LINE__, WDP_AUTO, "signal_program", 384, 100, WC_SIGNAL_PROGRAM, WC_BUILD_SIGNAL, WDF_CONSTRUCTION, std::begin(_nested_program_widgets), std::end(_nested_program_widgets) ); void ShowSignalProgramWindow(SignalReference ref) { uint32_t window_id = (ref.tile << 3) | ref.track; if (BringWindowToFrontById(WC_SIGNAL_PROGRAM, window_id) != nullptr) return; new ProgramWindow(&_program_desc, ref); }